volatile
文章目录
- volatile
- 可见性
- 指令重排
- volatile原理
volatile
可见性
问题:t 线程会从自己的高速缓存中读取run值,而无法获取到更新后的值,导致程序无法结束。
@Slf4j
public class Test05 {
static boolean run = true;
public static void main(String[] args) {
new Thread(() ->{
int i=0;
while(run){
i++;
}
}).start();
log.info("开始任务");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
run = false;
log.info("停止任务");
}
}
解决方法:给run加上volatile
(易变的)属性,避免从工作缓存中读取值,而必须到主缓存中获取值,保证数据在不同线程中的可见性。
static volatile boolean run = true;
指令重排
问题:r1的结果可能为0,因为actor2中的两行代码可能会发生指令重排,导致执行顺序颠倒。
int num = 0;
boolean ready = false;
// 线程1 执行此方法
public void actor1(I_Result r) {
if(ready) {
r.r1 = num + num;
}else {
r.r1 = 1;
}
}
// 线程2 执行此方法
public void actor2(I_Result r) {
num = 2;
ready = true;
}
解决:给ready添加volatile
关键字
volatile boolean ready = false;
volatile原理
volatile的底层实现原理是内存屏障,Memory Barrier(Memory Fence)
- 对 volatile 变量的写指令后会加入写屏障
- 对 volatile 变量的读指令前会加入读屏障
而有了屏障之后,可见性和有序性就有了很好的解决。
可见性:
- 写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
- 读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中新数据
有序性:
- 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
- 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
虽然volatile
可以保证可见性和有序性,但它并不能保证复合操作(如自增、自减或检查后执行逻辑)的原子性。因此,volatile
并不能替代同步机制(如synchronized
)来控制对共享资源的并发访问。