volatile关键字是轻量级的synchronized,volatile在并发编程中保证共享变量的可见性,当一个线程修改被volatile修饰的共享变量时,另一个线程就能读到这个修改的值。volatile能够保证共享变量的可见性但不能保证复合操作的原子性:好比像i++这样的操作是volatile是不能保证的。编程
被volatile修饰的变量有两种特性安全
1.保证此变量对全部线程的可见性,可见性是指当一条线程修改了这个变量的值,新的值对于其它是能够当即的得知的。虽然volatile对变量有可见性的特色,可是volatile并不能保证volatile++这样的操作在并发下是安全的。在深刻理解Java虚拟机里面做者从字节码的角度解释了不安全的缘由。具体的描述看书就能够了。下面是个人理解,volatile保证的变量读和写(对于不理解volatile的读写的内存语义的下面会讲到)的时候是安全的可是并不能保证对变量的操作是线程安全。什么意思呢,就是在i++的这样的操作JVM不仅是从内存(指的是JMM的内存)中获取值和存入值还作了其余的操作,好比iadd,当它执行像iadd这样的操作时其余线程可能将值已经修改了,而此时当前线程是没有执行获取操作的因此在当前线程操做的仍是旧值。对比JMM理解会比较容易理解一点。并发
2.禁止指令重排序关于重排序的问题看上一篇文章JMM。线程
volatile写的内存语义: 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存3d
volatile读的内存语义: 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量blog
为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。对于编译器来讲,发现一个最优布置来最小化插入屏障的总数几乎不可能。为此,JMM采起保守策略。下面是基于保守策略的JMM内存屏障插入策略排序
在每一个volatile写操做的前面插入一个StoreStore屏障。内存
在每一个volatile写操做的后面插入一个StoreLoad屏障。文档
在每一个volatile读操做的后面插入一个LoadLoad屏障。编译器
在每一个volatile读操做的后面插入一个LoadStore屏障。
如图
上图中的StoreStore屏障能够保证在volatile写以前,其前面的全部普通写操做已经对任意处理器可见了。这是由于StoreStore屏障将保障上面全部的普通写在volatile写以前刷新到主内存。
这里比较有意思的是,volatile写后面的StoreLoad屏障。此屏障的做用是避免volatile写与后面可能有的volatile读/写操做重排序。由于编译器经常没法准确判断在一个volatile写的后面是否须要插入一个StoreLoad屏障(好比,一个volatile写以后方法当即return)。为了保证能正确实现volatile的内存语义,JMM在采起了保守策略:在每一个volatile写的后面,或者在每一个volatile读的前面插入一个StoreLoad屏障。从总体执行效率的角度考虑,JMM最终选择了在每一个volatile写的后面插入一个StoreLoad屏障。由于volatile写-读内存语义的常见使用模式是一个写线程写volatile变量,多个读线程读同一个volatile变量。当读线程的数量大大超过写线程时,选择在volatile写以后插入StoreLoad屏障将带来可观的执行效率的提高。从这里能够看到JMM在实现上的一个特色:首先确保正确性,而后再去追求执行效率。
参考文档:
<< Java并发编程艺术>>方腾飞 魏鹏 程晓明
<<深刻理解Java虚拟机:JVM高级特性与最佳实践>>周志明