摘要
在 Java 并发编程中,很多开发者在调试时会发现:代码看起来顺序正确,但运行结果却出乎意料。根源就在于 指令重排 和 内存可见性 。Java 内存模型(JMM)为此定义了 happens-before 规则,它是我们推理并发程序执行结果的理论基石。本文将深入解析 happens-before 的核心规则、典型应用场景和常见误区。
一、为什么需要 happens-before?
我们先看一个简单的例子:
java
int a = 0;
boolean flag = false;
// 线程1
a = 1;
flag = true;
// 线程2
if (flag) {
System.out.println(a);
}
在单线程逻辑下,输出结果理应是 1
。
但在多线程下,可能输出 0,甚至不打印任何内容。
原因:
- 编译器和 CPU 可能会将
a=1
与flag=true
重排序。 - 线程 2 可能在看到
flag=true
时,还没看到a=1
的写入。
那么,我们如何保证"先写 a=1,再写 flag=true"这种顺序关系?
答案就是 ------ happens-before 规则。
二、happens-before 的定义
官方定义:
如果 A happens-before B,那么 A 的执行结果对 B 是可见的,并且 A 的执行顺序排在 B 之前。
注意两点:
- 不代表物理上的先后,而是逻辑上的因果关系。
- 不禁止重排序,但保证了"语义等价"的执行结果。
三、happens-before 的八大规则
JMM 定义了一系列规则来保证关键有序性。我们逐条解析:
1. 程序次序规则
在 单个线程 内,按照代码书写顺序,前面的操作 happens-before 后面的操作。
java
int x = 10; // A
int y = x+1; // B
保证 A happens-before B。
⚠️ 但这是 线程内保证,跨线程需要其他规则。
2. 监视器锁规则(synchronized)
对一个锁的 解锁 happens-before 后续对这个锁的 加锁。
java
synchronized(lock) { // 线程1
shared = 42;
}
synchronized(lock) { // 线程2
System.out.println(shared);
}
保证线程 2 一定能看到线程 1 的写入。
3. volatile 变量规则
对一个 volatile
变量的写,happens-before 后续对这个变量的读。
java
volatile boolean flag = false;
int a = 0;
// 线程1
a = 1;
flag = true;
// 线程2
if (flag) {
System.out.println(a); // 一定能看到 a=1
}
这里即便存在重排序,JMM 也会保证:线程 2 看到 flag=true
时,必然能看到 a=1
。
4. 传递性规则
如果 A happens-before B,B happens-before C,那么 A happens-before C。
java
// 线程1
a = 1; // A
flag = true; // B
// 线程2
if (flag) { // C
System.out.println(a);
}
由规则 3 可知:B happens-before C,A happens-before B,因此 A happens-before C。
5. 线程启动规则
Thread.start()
happens-before 线程中的 run() 方法。
java
int x = 0;
Thread t = new Thread(() -> {
System.out.println(x); // 一定能看到最新值
});
x = 10;
t.start();
保证在主线程中对 x
的修改,对新线程可见。
6. 线程终止规则
线程中的所有操作 happens-before 其他线程检测到该线程终止。
java
Thread t = new Thread(() -> {
value = 42; // 线程工作
});
t.start();
t.join();
System.out.println(value); // 一定能看到 42
join()
确保主线程看到子线程的所有结果。
7. 线程中断规则
对线程 interrupt()
的调用 happens-before 该线程检测到中断。
java
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 工作逻辑
}
});
t.start();
t.interrupt(); // happens-before 检测到中断
8. 对象终结规则(finalize)
对象的构造函数执行完成,happens-before 它的 finalize()
方法调用。
这条在实践中使用较少,但保证了对象销毁前的构造安全性。
四、happens-before 规则全景图

五、常见误区
误区 1:happens-before = 按顺序执行
并不是。JMM 允许重排序,只要不破坏 happens-before 的约束。
误区 2:volatile 能保证原子性
volatile 只保证可见性和有序性,复合操作(如 count++
)仍需加锁。
误区 3:synchronized 开销很大
在 JDK 1.6 之后,synchronized 已通过偏向锁、轻量级锁等优化,大多数情况下性能完全可接受。
六、实践应用场景
1. 双重检查锁单例(DCL)
需要 volatile
来防止指令重排。
2. 状态标志退出线程
使用 volatile boolean running = true;
,线程轮询时能及时看到最新值。
3. 跨线程结果可见
通过 join()
保证线程结果对主线程可见。
七、总结
- happens-before 是 JMM 中 保证有序性和可见性 的核心规则。
- 它通过 8 条规则,定义了线程之间的数据交互关系。
- 程序推理方法:只要能在规则中找到 A happens-before B,就能保证 A 的结果对 B 可见,顺序不会被破坏。
理解 happens-before,不仅能解释看似诡异的并发 bug,更能指导我们写出正确的多线程代码。