理解 volatile
JMM 对 volatile 的特殊规则
JVM 对 volatile 专门定义了特殊的访问规则:
在工作内存中,每次使用变量之前都要先从主内存刷新最新值,以保证可以看到其他线程对该变量修改的值
在工作内存中,每次修改变量后,都需要立刻同步回主内存,保证修改对其他线程可见
volatile 修饰的变量不会被指令重排序优化,保证代码的执行顺序与程序的顺序相同
volatile 的语义
可见性
保证变量对所有线程的可见性,可见性是指当一个线程改变了变量的值后,变量的新值在其他线程是可以立即得知的。(普通变量由于需要借助主内存在线程的工作内存中对变量进行周转,无法保证可见性)。虽然 volatile 保证了变量不会出现不一致的情况,但由于对变量执行的操作并不是都是原子性的,所以光靠 volatile 并不能保证线程安全,(例如 i++,需要先读取变量,再自增,再写回),还是需要加锁。
volatile 使用场景
禁止指令重排序
使用volatile 的第二个语义是禁止指令重排序优化。普通的变量只能保证在方法的执行过程中,所有依赖赋值结果的地方都能正确获取结果,但不能保证变量赋值操作的顺序与程序代码中的顺序一致。
比如下面的代码,如果 initialized 没有被 volatile 修饰,那么由于指令重排序优化,赋值语句initialized=true
有可能先于配置读取被执行,这就可能导致第二段代码执行出现问题。
volatile 禁止重排序的语义在 JDK 1.5 才被修复,之前的版本就算使用 volatile 也不能完全避免指令重排序,即在 JDK 1.5 之前无法通过 DCL 方式实现单例模式。
如何实现可见性和禁止重排序?
在对volatile 修饰的变量进行修改后,会通过增加内存屏障(空操作加lock前缀)的方式,将本CPU缓存写入到内存中,这会导致其他CPU缓存失效,所以其他CPU再次访问这个值时只能重新从内存读取,这就保证了变量在不同线程之间的可见性。
内存屏障将数据同步到内存后,也就意味着内存屏障前的指令都是执行完成的,后面的指令不能再被重排序到内存屏障之前。
最后更新于
这有帮助吗?