juc并发包中的happens-before 规则
一、happens-before 核心概念
happens-before 是 Java 内存模型(JMM)中定义的一套可见性与有序性规则,它规定:如果两个操作之间存在 happens-before 关系,那么前一个操作的结果一定对后一个操作可见,且前一个操作的执行顺序一定早于后一个操作。
抛开这些规则,JMM 无法保证一个线程对共享变量的写操作,对其他线程的读操作可见。
二、happens-before 八大核心规则
1. 程序顺序规则(Program Order Rule)
在单个线程内,按照代码的书写顺序,前面的操作 happens-before 于后面的操作。
解释:单线程中,代码的执行顺序与书写顺序一致,前面对变量的修改对后面的代码可见。
int a = 1;
int b = 2;
// 单线程内:a=1 happens-before b=2,a=1 对 b=2 可见
2. 监视器锁规则(Monitor Lock Rule)
线程解锁 m 之前对变量的写操作,happens-before 于接下来对 m 加锁的其他线程对该变量的读操作。
解释:synchronized 锁释放前的所有修改,对后续获取同一把锁的线程可见。
static int x;
static Object m = new Object();
// 线程 t1:先对 x 赋值,再释放锁
new Thread(() -> {
synchronized (m) {
x = 10; // 写操作
}
}, "t1").start();
// 线程 t2:后获取同一把锁,读取 x 的值
new Thread(() -> {
synchronized (m) {
// 一定能读到 x=10,因为 t1 解锁 happens-before t2 加锁
System.out.println(x);
}
}, "t2").start();
3. volatile 变量规则(Volatile Variable Rule)
对一个 volatile 变量的写操作 happens-before 于后续对该变量的读操作。
解释:volatile 禁止指令重排序,同时保证写操作的结果立即刷新到主内存,读操作从主内存读取最新值。
static volatile boolean flag = false;
static int a = 0;
// 线程 t1:先修改 a,再修改 volatile flag
new Thread(() -> {
a = 10;
flag = true; // volatile 写
}, "t1").start();
// 线程 t2:先读 volatile flag,再读 a
new Thread(() -> {
if (flag) { // volatile 读
// 一定能读到 a=10,因为 flag 的写 happens-before 读
System.out.println(a);
}
}, "t2").start();
4. 线程启动规则(Thread Start Rule)
Thread 对象的 start () 方法 happens-before 于该线程内的任何操作。
解释:主线程中调用
t.start()之前的所有修改,对新启动的线程 t 可见。
int a = 10;
Thread t = new Thread(() -> {
// 一定能读到 a=10,因为 main 线程的 a=10 happens-before t 线程的 run()
System.out.println(a);
});
a = 20;
t.start(); // start() 之前 a=20,所以 t 线程读到 a=20
5. 线程终止规则(Thread Termination Rule)
线程内的所有操作 happens-before 于其他线程检测到该线程已经终止(如 t.join() 成功返回、t.isAlive() 返回 false)。
解释:线程 t 执行完的所有修改,对调用
t.join()等待它结束的线程可见。
int a = 0;
Thread t = new Thread(() -> {
a = 30;
});
t.start();
t.join(); // 等待 t 结束
// 一定能读到 a=30,因为 t 内的 a=30 happens-before join() 返回
System.out.println(a);
6. 线程中断规则(Thread Interruption Rule)
对线程的 interrupt() 调用 happens-before 于被中断线程检测到中断事件(如 Thread.interrupted()、InterruptedException)。
解释:调用
t.interrupt()之前的修改,对 t 线程中处理中断的代码可见。
static int b = 0;
Thread t = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 能读到 b=50,因为 interrupt() 调用 happens-before 异常捕获
System.out.println(b);
}
});
t.start();
b = 50;
t.interrupt(); // 中断 t 线程
7. 对象终结规则(Finalizer Rule)
一个对象的构造函数执行结束 happens-before 于它的 finalize() 方法开始执行。
解释:对象初始化完成后,才会被垃圾回收并执行 finalize (),构造阶段的修改对 finalize () 可见。
class Demo {
int c = 0;
public Demo() {
c = 60;
}
@Override
protected void finalize() throws Throwable {
// 一定能读到 c=60,因为构造函数结束 happens-before finalize()
System.out.println(c);
}
}
8. 传递性规则(Transitivity Rule)
如果操作 A happens-before 操作 B,且操作 B happens-before 操作 C,那么 A happens-before 操作 C。
解释:happens-before 关系可以传递,形成可见性的连锁效应。
static volatile int d = 0;
// A:d=70(volatile 写)
// B:读取 d(volatile 读)
// C:基于 d 的后续操作
// 若 A happens-before B,B happens-before C,则 A happens-before C
三、规则总结与使用提示
| 规则类型 | 核心可见性保障 | 典型应用场景 |
|---|---|---|
| 程序顺序规则 | 单线程内代码顺序可见 | 基础代码逻辑 |
| 监视器锁规则 | 锁释放前的写 → 锁获取后的读可见 | synchronized 同步块 |
| volatile 变量规则 | volatile 写 → 后续 volatile 读可见 | 状态标记、双重检查锁定(DCL) |
| 线程启动规则 | start () 前的写 → 新线程内的读可见 | 线程初始化传参 |
| 线程终止规则 | 线程内的写 → join () 后的读可见 | 线程结果汇总 |
| 线程中断规则 | interrupt () 前的写 → 中断处理可见 | 中断响应逻辑 |
| 对象终结规则 | 构造函数的写 → finalize () 的读可见 | 对象资源清理 |
| 传递性规则 | 连锁可见性,组合其他规则使用 | 复杂多线程流程控制 |
关键提示
- happens-before 不是 “时间上先执行”,而是可见性与有序性的保证,即使指令重排序,也不能破坏这种关系。
- 只有满足上述规则,JMM 才会保证多线程间的变量可见性,否则可能出现 “脏读”“指令重排序导致的结果异常”。
- 实际开发中,synchronized、volatile、Lock、Thread.join() 等 API 底层都依赖这些规则实现可见性与有序性。
四、综合示例:多线程下的可见性验证
public class HappensBeforeDemo {
private static volatile boolean flag = false;
private static int num = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
num = 100; // 1. 普通写
flag = true; // 2. volatile 写(happens-before 规则3)
}, "t1");
Thread t2 = new Thread(() -> {
if (flag) { // 3. volatile 读(happens-before 规则3)
// 4. 传递性保证:1 happens-before 2,2 happens-before 3,3 happens-before 4 → 1 happens-before 4
System.out.println(num); // 一定输出 100
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
}
}
解释:
- 程序顺序规则:
num=100happens-beforeflag=true- volatile 规则:
flag=truehappens-beforeif(flag)- 传递性规则:
num=100happens-beforeSystem.out.println(num),因此一定能读到num=100。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)