volatile 禁止指令重排
本文介绍下volatile禁止指令重排的背景及最终效果。
volatile 可见性保证
我们都知道 volatile 会保证读写修饰的变量时会将对应变量同步给主存。
但是实际上, volatile 修饰的变量不仅保证自身,还会保证其他局部变量的可见性。
以下结合例子讲解下。
public class MyClass {
private int years;
private int months;
private volatile int days;
// 读主存的时候,会把后边的变量一起顺带读出来
public int totalDays() {
int total = this.days;
total += months * 30;
total += years * 365;
return total;
}
// 写主存的时候, 会把前边的变量一起顺带写进去
public void update(int years, int months, int days){
this.years = years;
this.months = months;
this.days = days;
}
}
复制代码
Reordinrg 指令重排的挑战
指令重排前的代码
int a = 1;
int b = 2;
a++;
b++;
复制代码
可能的重排后代码
// 估计是为了读写操作一起,减少内存缺页率
int a = 1;
a++;
int b = 2;
b++;
复制代码
对volatile可见性保证的影响
// 指令重排前,写主存的时候, 会把前边的变量一起写进去
public void update(int years, int months, int days){
this.years = years;
this.months = months;
this.days = days;
}
// 指令重排后,写主存的时候, 本来该顺带写的变量不写了
public void update(int years, int months, int days){
this.days = days;
this.years = years;
this.months = months;
}
复制代码
hanppens-before 发生前保证
为了保证 vaolatile 对其他变量可见性的保证 规则, volatile 的 happens-before 规定对指令重排的限制。
读写其他变量如果本身在写volatile变量之前的, 禁止重排到写volatile变量之后。
读写其他变量如果本身在读volatile变量之后的, 禁止重排到读volatile变量之前。
据说 happens-before 是 JSR-133 的规范质疑,内存屏障是 CPU 的指令。
前者是目的, 后者是实现目的的手段。
本文主要参考
近期评论