class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
// 这里x会是多少呢?
}
}
}
主要是通过内存屏障(memory barrier)禁止重排序的,即时编译器根据具体的底层体系架构,将这些内存屏障替换成具体的 CPU 指令。对于编译器而言,内存屏障将限制它所能做的重排序优化。而对于处理器而言,内存屏障将会导致缓存的刷新操作。比如,对于volatile,编译器将在volatile字段的读写操作前后各插入一些内存屏障。
class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
// 这里x会是多少呢?
}
}
}
解答:
程序顺序性规则是针对单线程的。如果只考虑单线程,那么编译器可以对范例代码进行指令重排优化。但是对于多线程,volatile 变量规则、传递性这 2 条规则,就附加了新的限制。对于多线程,这 3 条 happens-before 规则要求,线程 B 在读到 v = true 的时候,也能见到 x = 42。如果编译器仍然按照单线程的情况,对这两条语句进行指令重排,把 v = true 放到 x = 42 之前。那么,线程 B 就有可能看不到 x 的值为 42。这显然违背了 happens-before 的规定。编译器为了符合规则,只能不进行指令重排优化了。
为了符合 happens-before 规定,对于示例代码,编译器不能进行指令重排的编译优化。但实际上,仅仅不进行指令重排编译优化,并不能保证编译后的代码的执行结果,符合 happnes-before 规定。因为指令重排这一优化措施,并不仅仅是编译器会做。现代 cpu 在执行机器指令的时候,同样会做指令重排的优化。所以,如果 cpu 在执行机器指令时,发生了机器指令的重排序。上述实例代码的结果,仍然有机会不符合 happens-before 规定。
为了令到编译后的代码的执行结果,能够符合 happens-before 规定。编译器除了不能做指令重排序编译优化之外,还要在生成的机器代码中。加入特定 cpu 指令,令到 cpu 只会执行完这条特定指令之后,才会执行后续的其它机器指令。而这种特定指令,是通过建立 “内存屏障” 来禁止 cpu 的指令重排序的。那为什么叫 “内存屏障” 呢?可以理解为一种特殊指令,要求 cpu 把缓存数据写回到主内存中。这就像在内存中建立了一道屏障,令到后面的代码不能越过屏障,提前执行。