Volatile原理与分析
我们知道并发编程的三大特性:
- 原子性:保证指令不会受到线程上下文切换的影响
- 有序性:保证指令不会受 cpu 指令并行优化的影响
- 可见性:保证指令不会受 cpu 缓存的影响
volatile可以保证共享变量的有序性和可见性,那么今天来分析和介绍其底层原理。
底层原理
底层实现原理是内存屏障,Memory Barrier (Memory Fence)
- 对volatile变量的写指令后会加入写屏障
- 对volatile变量的读指令后会加入读屏障
public void actor1(){
num = 2;
raedy = true; // raedy是volatile赋值带写屏障
// 写屏障
}
public void actor2(Result r){
// 读屏障
// ready是volatile读取值带读屏障
if(ready){
r = num + num;
} else {
r = 1;
}
}
sequenceDiagram
participant t1 as t1 线程
participant num as num = 0
participant ready as volatile ready = false
participant t2 as t2 线程
t1 -->> t1: num = 2
t1 ->> ready: ready = true
Note over t1, ready: 写屏障
Note over num, t2: 读屏障
t2 ->> ready: 读取ready=true
t2 ->> num: 读取num=2
如何保证可见性
写屏障:保证在改屏障之前的,对共享变量的改动,都同步到主存中
读屏障:保证在改屏障之后,对共享变量的读取,加载的是主存中的最新数据
如何保证有序性
写屏障会确保指令重排序时,不会将写屏障之后的代码排在屏障之后
读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
不足
无法解决指令交错的问题
- 写屏障仅仅是保证之后的读能够读到新的结果,但不能保证读跑到它前面去
- 有序性的保证也只是保证了本线程内相关代码不被重排序
以i++为例:
sequenceDiagram
participant t1 as t1 线程
participant i as volatile i = 0
participant t2 as t2 线程
t2 -->> i: 读取i= 0
t1 -->> i: 读取i=0
t1 -->> t1: i+1
t1 -->> i: 写入i=1
t2 -->> t2: i-1
t2 -->> i: 写入i=-1