在面试中,经常会被问到volatile关键字的作用和原理,故而整理出本篇文章进行说明。
(提示:阅读本文需要具备内存操作相关知识,可以看这里)
volatile是Java保留的一个关键字,可以用于修饰变量,有以下两个语义:
1. 保证此变量对所有线程的可见性
Java内存模型保证了对于volatile修饰的变量,load和use操作必须连续出现,assign和store操作必须连续出现。这样保证了工作内存每次使用变量时都从主内存刷新最新变量值,每次修改变量后都向主内存写入最新变量值。
但是,需要注意的是,即使如此,volatile也无法保持一致性,因为use操作将变量传递给执行引擎之后的运算不能保证原子性,即使是基础数据类型,也只有访问读写操作具有原子性,数据运算的过程无法保证。所以,虽然从各个线程读写的角度来看数据一致,但实际操作中会存在覆盖运算结果的情况。
2. 禁止指令重排序优化
Java字节码解释为机器指令后,只能保证结果一致,并不能生成的汇编代码机器指令的顺序也和Java代码中一致。在单一线程中,指令重排序不会造成什么问题,但在并发环境下,不做任何操作则无法保证代码按照原有的期望去执行。
volatile关键字修饰的变量会在assign操作对应的指令之后加上一句“lock addl $0x0,(%esp)”(注意这里的lock和内存操作lock无关),语义为对ESP寄存器加0,即一个空操作。这一指令会要求将线程所使用的缓存写入内存,可以理解为将工作内存中的变量写入主内存,即store和write操作。
因为指令重排序要保证结果一致,所以对于这类写回操作不会重排,即“无法越过内存屏障”。
这一规则保证了对于两个volatile变量A和B,若A的use或assign操作先于B,则A的read或write操作也先于B。
应用场景
结合以上所述的两条语义,使用volatile修饰变量可以用于以下场景:
1. 运算结果并不依赖变量当前值(即只依赖于原子性的写操作),或者确保只有单一线程可以修改变量的值;
2. 变量不需要其他状态变量共同参与不变约束。
参考文献:
《深入理解Java虚拟机:JVM高级特性与最佳时间》,第2版,周志明