参考书籍:《Java 并发编程的艺术》 - 方腾飞 魏鹏 程晓明著
在并发编程中,synchronized 和 volatile 这两个关键字都扮演着重要的角色,volatile 是轻量级的 synchronized,它在多处理器开发中保证了共享变量的 “可见性”。
那么什么是 “可见性” 呢?
可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值
如果 volatile 使用得当的话,它会比 synchronized 的成本更低,因为它不会造成线程的阻塞,也就不会导致上下文切换和调度,所以性能更好,开销更低。
volatile 定义
Java 语言规范第 3 版对 volatile 的定义如下:
Java 编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排它锁单独获得这个变量。
Java 提供了 volatile,在某些情况下比锁要更加方便。
如果一个字段被声明为 volatile,Java 的线程内存模型(JMM)确保所有线程看到这个变量的值是一致的。
volatile 实现原理
看一个简单的例子:
private volatile Singleton instance = null;
public Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
instance 这个成员变量是被 volatile 关键字修饰的,所以 instance 这个引用的改变对于各个线程来说是 “可见的”。那么 volatile 是如何保证可见性的呢?
我们在 x86 处理器下通过工具获取 JIT 编译器生成汇编指令来查看对 volatile 进行写操作时,CPU 会做什么事情(就是这句代码:instance = new Singleton();
)。
转成汇编代码如下:
0x01a3deld: movb $0x0,0x1104800(%esi);0x01a3de24: lock add1 $0x0,(%esp);
有 volatile 变量修饰的共享变量进行写操作的时候会多出第二行汇编代码,Lock 前缀的指令在多核处理器下会引发两件事情:
- 将当前处理器缓存行的数据写回到系统内存。
- 这个写回内存的会使在其他 CPU 里缓存了该内存地址的数据无效。
正是这两个额外的事件,使得 volatile 保证了共享变量的 “可见性”。