1. 保证可见性
- 写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
public void actor2(I_Result r) {
num = 2;
ready = true; // ready 是 volatile 赋值带写屏障
// 写屏障
}
- 读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据
public void actor1(I_Result r) {
// 读屏障
// ready 是 volatile 读取值带读屏障
if(ready) {
r.r1 = num + num;
} else {
r.r1 = 1;
}
}
2. 如何保证有序性
- 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
public void actor2(I_Result r) {
num = 2;
ready = true; // ready 是 volatile 赋值带写屏障
// 写屏障
}
- 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
public void actor1(I_Result r) {
// 读屏障
// ready 是 volatile 读取值带读屏障
if(ready) {
r.r1 = num + num;
} else {
r.r1 = 1;
}
}
3. 小结
volatile 不能解决指令交错(不可以保证原子性):
- 写屏障仅仅是保证之后的读能够读到最新的结果,但不能保证读跑到它前面去
- 而有序性的保证也只是保证了本线程内相关代码不被重排序
4. balking模式
希望 doInit() 方法仅被调用一次,下面的实现是否有问题,为什么?
分析:
- initialized 被 volatile 修饰 只可以保证变量的可见性,并不能保证多行代码执行的原子性。
- volatile 适用于一个线程写 另外一些线程在读的情况。还可以用于代码在必要的时间禁用指令重排序。
改正方法:
public class TestVolatile {
Boolean initialized = false;
void init() {
synchronized (initialized) {
if (initialized) {
return;
}
doInit();
initialized = true;
}
}
private void doInit() {
}
}