【Java】【并发编程】volatile关键字
前言
特性
- 对所有线程可见;
- 防止指令重排;
可见性
指当一条线程修改了某个volatile变量的值,新值对于其它线程来说是可以立即知道的,而普通变量无法做到这点。
误区
由于volatile对所有线程立即可见,对volatile的写操作会立即反应到其它线程,因此认为基于volatile的变量的运算在并发下是安全的,这是错误的。
volatile所谓的其它线程立即知道,是其它线程在使用的时候会read主内存然后load到自己工作内存,如果这时候其它线程进行了修改,本线程的volatile变量状态会被置为无效,会重新读取,但如果本线程的变量已经被读入执行栈帧,那么是不会重新读取的。
那么两个线程都把本地工作内存内容写入主存的时候就会发生覆盖问题,导致并发错误。
防止指令重排
重排序优化是机器级的操作,也就是硬件级别的操作。重排序会打乱代码顺序执行,但会保证在执行过程中所有依赖赋值结果的地方都能获取到正确的结果,因此在一个线程的方法执行过程中无法感知到重排的操作影响,这也是“线程内表现为串行”的由来。
volatile的屏蔽重排序在jdk1.5后才被修复。原理是volatile生成的汇编代码多了一条带lock前缀的空操作的命令,这个lock前缀会使得本cpu的缓存写入内存,而写入动作也会引起别的cpu或者别的内核无效化,这相当于对cpu缓存中的变量做了一次store跟write的操作,所以通过这样一个操作,可以让变量对其它cpu立即可见(因为状态被置为无效,用的话必须重新读取)。
特殊规则:
- 每次使用变量之前都必须先从主内存刷新最新的值,用于保证能看见其它线程对变量的修改;
- 每次对变量修改后都必须立刻同步到主内存中,用于保证其它线程可以看到自己的修改;
- 两个变量都是volatile的,将数据同步到内存的时候,先读的先写;
原理
volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的(多出一个lock前缀指令):
- 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;
- 即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
- 它会强制将对缓存的修改操作立即写入主存;
- 如果是写操作,它会导致其他CPU中对应的缓存行无效。
适用场景
- 条件:对变量的写操作不依赖于当前值;该变量没有包含在具有其他变量的不必要的式子中。
- 场景:特别适合作为状态标记量;检查两次
volatile boolean inited = false;
// 线程1
context = loadContext();
inited = true;
// 线程2:
while(!inited){
sleep();
}
doSomethingWithConfig(context);
参考
- https://blog.csdn.net/zhanghai412/article/details/115548832
- https://www.cnblogs.com/nevermorewang/p/9461417.html
- https://blog.csdn.net/u012723673/article/details/80682208