目录
- 1. 如何解决重排序带来的问题
- 2. happens-before
1. 如何解决重排序带来的问题
对于编译器,JMM 的编译器重排序规则会禁止特定类型的编译器重排序。对于处理器重排序,JMM 的处理器重排序规则会要求编译器在生成指令序列时,插入特定类型的内存屏障(Memory Barries / Memory Fence)指令,通过内存屏障指令来禁止特定类型的处理器重排序。
由于常见的处理器内存模型比 JMM 要弱,Java 编译器在生成字节码时,会在执行指令序列的适当位置插入内存屏障来限制处理器的重排序。同时,由于各种处理器内存模型的强弱不同,为了在不同的处理器平台向程序员展示一个一致的内存模型,JMM 在不同的处理器中需要插入的内存屏障的数量和种类也不同。
CPU内存屏障:
- LoadLoad:禁止读和读的重排序;
- StoreStore:禁止写和写的重排序;
- LoadStore:禁止读和写的重排序;
- StoreLoad:禁止写和读的重排序。
Java内存屏障:
public final class Unsafe {
// LoadLoad + LoadStore
public native void loadFence();
// StoreStore + LoadStore
public native void storeFence();
// loadFence() + storeFence() + StoreLoad
public native void fullFence();
}
2. happens-before
JMM 使用 happens-before 规则来阐述操作之间的内存可见性,以及什么时候不能重排序。
在 JMM 中, 如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在 happens-before 关系。换个角度来说,如果A happens-before B,则意味着A的执行结果必须对B可见,也就是保证跨线程的内存可见性。
其中,4条规则与程序员密切相关。
- 程序顺序规则:一个线程中的每个操作,happens-before 于该线程中的任意后续操作;
- volatile 变量规则:对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读;
- synchronized 规则:对一个锁的解锁,happens-before 于随后对这个锁的加锁;
- 传递性:若 A happens-before B,且 B happens-before C,则 A happens-before C;
- start() 规则:若线程 A 执行 ThreadB.start(),则线程 A 的这个操作 happens-before 于线程 B 中的任意操作;
- join() 规则:若线程 A 执行 ThreadB.join() 并成功返回,那么线程 B 中的任意操作 happens-before 于线程 A 从 ThreadB.join() 的成功返回。
举个栗子:
假设线程 A 执行 writer() 方法之后,线程 B 执行 reader() 方法。根据 happens-before 规则,这个过程建立的 happens-before 关系可以分为 3 类:
- 根据顺序规则,1 happens-before 2,3 happens-before 4;
- 根据 volatile 规则,2 happens-before 3;
- 根据 happens-before 的传递性规则,1 happens-before 4。
public class Test2 {
int a = 0;
volatile boolean flag = false;
public void writer() {
// 1
a = 1;
// 2
flag = true;
}
public void reader() {
// 3
if (flag) {
// 4
int i = a;
}
}
}
更多关于「happens-before 和 as-if-serial 语义」请详细看这篇文章->>Java并发编程之happens-before和as-if-serial语义