为了便于自己阅读理解,本文整理自《深入理解Java虚拟机》第3版。
Java内存模型
理解volatile关键字之前需要先了解下Java的内存模型。
- 主内存:Java虚拟机规定所有的变量(不是程序中的变量,它包括了实例字段、静态字段和构成数组对象的元素,但不包括局部变量和方法参数,因为后者是线程私有的)都存储在主内存。
- 工作内存:Java虚拟机中每个线程都有自己的工作内存,它保存了被线程使用的变量的主内存副本,线程对变量的读写操作都必须在工作内存中进行,不能直接读写主内存中的数据。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
volatile的可见性
"可见性"是指当一条线程修改了某个变量的值,新的值对于其他线程来说是可以立即得知的。而普通变量并不能做到这一点,它们均需要通过主内存来完成。比如,线程A修改一个普通变量的值,然后向主内存进行回写,另外一条线程B在线程A回写完成之后再对主内存进行读取操作,新变量值才会对线程B可见。
虽然volatile有"可见性"的特点,但是并不是“基于volatile变量的运算在并发下都是线程安全的"。
有些代码看似只有一行但实则在字节码(或更底层的机器码)层面它是多条指令,比如a++就是由“取值”,“加一”,“回写”这三个步骤的字节码指令构成的,在取值的时候volatile可以确保它在此时此刻是正确的值,但是在执行“加一”这一步骤的时候,其他线程可能已经把a的值改变了,这时再执行“回写”步骤,会把旧数据同步回主内存中。
所以volatile的“可见性”只能用在运算结果不依赖变量当前值的场景下,或者确保只有单一的线程会修改变量的值。
比如下面的例子,当stop()方法被调用时,能保证所有线程中执行的loop()方法能够立刻停下来。
volatile boolean stop;
void stop() {
stop = true;
}
void loop() {
while (!stop) {
doWork();
}
}
禁止指令重排序优化
指令重排序是指处理器采用了允许将多条指令不按程序规定的顺序分开发送给各个相应的电路单元进行处理,从逻辑上并不会打乱指令之间的依赖关系,依旧能得出正确的执行结果。比如现在有指令123按顺序编写,指令2依赖指令1的结果,指令3不依赖指令1和指令2的结果,那么指令3可以被排到指令1前面执行,指令2不会被排到指令1的前面去。
volatile可以避免这种情况发生,如果指令3是volatile修饰的变量,那么指令3可以确保在指令1和指令2之后执行,因为它会添加一个内存屏障,使得指令重排序无法将后面的指令重排序到内存屏障前面的位置。
所以在做一些初始化标志位变量的赋值的时候,可以使用volatile确保标志位不会在初始化完成前被赋值为true。
volatile boolean init;
void init() {
//...真正的初始化工作
init = true;
}