Skip to content

Happens-Before

JMM(Java 内存模型)最核心的概念。

理解 happens-before,是理解 synchronized、volatile、锁等一切并发机制的基础。

为什么需要 Happens-Before

并发环境下,程序执行顺序远没有单线程那么「线性」:

源代码顺序:                    实际执行顺序(可能):
a = 1;                        b = 2;
b = 2;                        a = 1;       ← 重排序
x = a + b;                    x = a + b;

在单线程中,这没问题(as-if-serial 语义保证结果一致)。但在多线程中,如果缺乏适当的同步,这种重排序可能导致可见性问题

线程A:                          线程B:
x = 1;                         while (y == 0) { }
y = 1;                         print(x);    ← 可能是 0!

happens-before 规则定义了:哪些操作的顺序必须被保证,哪些可以被优化。

Happens-Before 的含义

A happens-before B 意味着:

  1. 可见性:A 对共享变量的修改,B 一定能看到
  2. 有序性:A 不会重排序到 B 之后

注意:happens-before 不是时间上的先后,而是内存操作的偏序关系

A happens-before B  ≠  A 先于 B 执行
A happens-before B  =  A 的结果对 B 可见,A 在 B 之前

八大规则

规则含义
程序顺序规则单线程中,前面的操作 happens-before 后面的操作
监视器锁规则unlock happens-before 后续的 lock
volatile 规则volatile 写 happens-before 后续的 volatile 读
线程启动规则start() happens-before 线程内的所有操作
线程终止规则线程内所有操作 happens-before 其他线程检测到终止
线程中断规则interrupt() happens-before 检测到中断
终结器规则构造函数 happens-before finalize()
传递性规则A HB B,B HB C → A HB C

程序顺序规则

单线程中,代码写在前面的操作,happens-before 后面的操作:

java
public class ProgramOrderDemo {

    public static void main(String[] args) {
        int a = 1;      // 操作1
        int b = 2;      // 操作2
        int c = a + b;  // 操作3

        // 操作1 HB 操作2 HB 操作3
        // 所以 c 一定是 3
        System.out.println(c);
    }
}

编译器/CPU 可能重排序,但结果不变。

监视器锁规则

java
public class MonitorLockDemo {

    private static int value = 0;
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                value = 1;  // A: 写操作
            }              // unlock: 释放锁,刷新缓存
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                // lock: 获取锁,刷新缓存
                int x = value;  // B: 读操作
                System.out.println("读取到: " + x);
            }
        });

        t1.start();
        t2.start();

        // 锁规则:t1 的 unlock HB t2 的 lock
        // 所以 t2 一定能读取到 value=1
    }
}

volatile 规则

java
public class VolatileHBDemo {

    private static int value = 0;
    private static volatile boolean ready = false;

    public static void main(String[] args) throws InterruptedException {
        Thread writer = new Thread(() -> {
            value = 42;      // A
            ready = true;    // B (volatile 写)
        });

        Thread reader = new Thread(() -> {
            while (!ready) { // C (volatile 读)
                Thread.yield();
            }
            int x = value;   // D
            System.out.println("读取到: " + x);
        });

        // volatile 规则:A HB B,B HB C
        // 程序顺序:A HB B,C HB D
        // 传递性:A HB C,A HB D
        // 所以 x 一定等于 42

        writer.start();
        reader.start();
    }
}

这就是 DCL 单例模式中 volatile 的理论基础。

线程启动规则

java
public class ThreadStartDemo {

    private static int sharedValue = 0;

    public static void main(String[] args) throws InterruptedException {
        sharedValue = 42;  // 主线程赋值
        Thread thread = new Thread(() -> {
            // 线程启动规则:start() HB 线程内部所有操作
            System.out.println("读取到: " + sharedValue);
        });

        thread.start();    // start() 发生
        thread.join();     // 等待线程结束
    }
}

子线程一定能看到主线程在 start() 之前的所有操作。

线程终止规则

java
public class ThreadTerminationDemo {

    private static int result = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            result = 42;
        });

        thread.start();
        thread.join();  // join() 返回时,子线程的所有操作已完成

        // 线程终止规则:子线程操作 HB join() 返回
        // 所以一定能读取到 result=42
        System.out.println("结果: " + result);
    }
}

传递性规则

java
public class TransitivityDemo {

    private static volatile int a = 0;
    private static volatile int b = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            a = 1;        // A1
            b = 2;        // A2 (volatile)
        });

        Thread t2 = new Thread(() -> {
            while (b != 2) {  // B1 (volatile)
                Thread.yield();
            }
            int x = a;        // B2
            System.out.println("x = " + x);
        });

        // 分析:
        // A1 HB A2(程序顺序)
        // A2 HB B1(volatile)
        // B1 HB B2(程序顺序)
        // 传递性:A1 HB B2
        // 所以 x 一定等于 1

        t1.start();
        t2.start();
    }
}

综合应用:线程安全的单例

java
public class SafeSingleton {

    private static volatile SafeSingleton instance;

    public static SafeSingleton getInstance() {
        if (instance == null) {
            synchronized (SafeSingleton.class) {
                if (instance == null) {
                    instance = new SafeSingleton();
                    // 构造函数中的操作 HB 实例赋值(传递性)
                    // volatile 规则:赋值 HB 后续的 volatile 读
                }
            }
        }
        return instance;
    }

    private SafeSingleton() {}
}

happens-before 分析:

构造函数 → 实例赋值 → volatile 写

              volatile 读 → 后续操作(getInstance())

规则记忆口诀

单线程内:前 HB 后
锁:unlock HB lock
volatile:写 HB 读
线程通信:start HB 线程内,线程内 HB join
传递性:A HB B,B HB C → A HB C

注意事项

  1. happens-before 不是执行顺序:只是保证可见性和有序性
  2. 单线程不需要考虑:as-if-serial 语义保证正确性
  3. 跨线程必须满足:否则可能出现数据竞争
  4. 不满足 happens-before 的代码:行为未定义,可能出现任何结果
  5. volatile 的双重作用:防止重排序 + 保证可见性

要点回顾

  • happens-before 定义了内存操作的偏序关系
  • 满足 happens-before 的操作,结果对其他线程可见
  • 八大规则:程序顺序、监视器锁、volatile、线程启动/终止/中断、终结器、传递性
  • 传递性是组合规则,将多个 happens-before 串联起来
  • 理解 happens-before 是理解 Java 并发机制的基础

相关链接

基于 VitePress 构建