volatile关键字
我们都知道 volatile这个关键字,使用它在多线程环境下能保证该变量的内存可见性;这是如何实现的呢?Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁要更加方便。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。
其具体实现还是需要靠底层硬件和指令层面的支持:
带有这个关键字修饰的变量,在进行写操作的时候,汇编指令会增加一个“lock”操作,该操作的影响如下,
将当前处理器缓存的该变量的值写回到主内存,更新主内存变量的“版本号”;其他处理器内缓存的变量“版本号” 和主内存不一致导致失效。这么做的目的就是保证任何时刻读取到的该变量都是最新值。
而,volatile这个关键字还有另外一个功能,那就是限制指令重排。
所谓“重排”,就是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。在JVM的实现里,重排有一定的原则:编译器和处理器可能会对操作做重排序。编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。 这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。JMM针对编译器指定的volatile重排序规则如下:
·当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。这个规则确保 volatile写之前的操作不会被编译器重排序到volatile写之后。
·当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保 volatile读之后的操作不会被编译器重排序到volatile读之前。
·当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。
为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能。为此,JMM采取保守策略。
下面是基于保守策略的JMM内存屏障插入策略。·在每个volatile写操作的前面插入一个StoreStore屏障。
·在每个volatile写操作的后面插入一个StoreLoad屏障。
·在每个volatile读操作的后面插入一个LoadLoad屏障。
·在每个volatile读操作的后面插入一个LoadStore屏障。