咱们都知道 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屏障。