一、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 () 的读可见 对象资源清理
传递性规则 连锁可见性,组合其他规则使用 复杂多线程流程控制

关键提示

  1. happens-before 不是 “时间上先执行”,而是可见性与有序性的保证,即使指令重排序,也不能破坏这种关系。
  2. 只有满足上述规则,JMM 才会保证多线程间的变量可见性,否则可能出现 “脏读”“指令重排序导致的结果异常”。
  3. 实际开发中,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=100 happens-before flag=true
  • volatile 规则:flag=true happens-before if(flag)
  • 传递性规则:num=100 happens-before System.out.println(num),因此一定能读到 num=100
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐