小熊回收站|分析:volatile内存屏障+实现原理(JMM和MESI)( 五 )


  1. 当第二个操作是volatile写时 , 不管第一个操作是什么 , 都不能重排序 。 这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后 。
  2. 当第一个操作是volatile读时 , 不管第二个操作是什么 , 都不能重排序 。 这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前 。
  3. 当第一个操作是volatile写 , 第二个操作是volatile读时 , 不能重排序 。
为了实现volatile的内存语义 , 编译器在生成字节码时 , 会在指令序列中插入内存屏障来禁止特定类型的处理器重排序 。 对于编译器来说 , 发现一个最优布置来最小化插入屏障的总数几乎不可能 。 为此 , JMM采取保守策略 。 下面是基于保守策略的JMM内存屏障插入策略:
  • 在每个volatile写操作的前面插入一个StoreStore屏障 。
  • 在每个volatile写操作的后面插入一个StoreLoad屏障 。
  • 在每个volatile读操作的后面插入一个LoadLoad屏障 。
  • 在每个volatile读操作的后面插入一个LoadStore屏障 。
最后需要特别提一下原子性 , Java语言规范鼓励但不强求JVM对64位的long型变量和double型变量的写操作具有原子性 。 当JVM在这种处理器上运行时 , 可能会把一个64位long/double型变量的写操作拆分为两个32位的写操作来执行 , 这两个32位的写操作可能会被分配到不同的总线事务中执行 , 此时对这个64位变量的写操作将不具有原子性 。 锁的语义决定了临界区代码的执行具有原子性 。 但是因为一个volatile变量的读 , 总是能看到(任意线程)对这个volatile变量最后的写入 , 所以即使是64位的long型和double型变量 , 只要它是volatile变量 , 对该变量的读/写就具有原子性 。 但是多个volatile操作或类似于i++这种复合操作 , 这些操作整体上不具有原子性 。 针对于复合操作如i++这种 , 如果要保证原子性 , 需要通过synchronized关键字或者加其他锁来处理 。 注意:在JSR-133之前的旧内存模型中 , 一个64位long/double型变量的读/写操作可以被拆分为两个32位的读/写操作来执行 。 从JSR-133内存模型开始(即从JDK5开始) , 仅仅只允许把一个64位long/double型变量的写操作拆分为两个32位的写操作来执行 , 任意的读操作在JSR-133中都必须具有原子性(即任意读操作必须要在单个读事务中执行) 。
作者:双子孤狼
原文链接:


推荐阅读