JAVA:什么是 happens-before 原则?

admin
1
2025-08-01

🚦 1、简述

在多线程开发中,我们经常会遇到“明明修改了变量,为什么另一个线程却读取不到”的问题。这并不是代码写错,而是你忽略了 Java 内存模型(JMM) 中的核心规则之一 —— happens-before 原则

image-wmit.png


📘 2、什么是 happens-before 原则?

happens-before 是 Java 内存模型(JMM)中的一条可见性规则,它规定了一个操作对另一个操作的执行顺序可见性

如果一个操作 A happens-before 操作 B,那么 A 的结果对 B 是可见的,并且 A 的执行排在 B 之前。

换句话说,A 先发生,B 后发生,并且 B 能“看到”A 的结果

JMM 中的几个 happens-before 规则:

🔹 程序顺序规则
同一个线程中的操作,按照代码顺序依次执行。

🔹 锁定规则(synchronized)
对一个锁的解锁(unlock)操作,happens-before 之后对这个锁的加锁(lock)操作。

🔹 volatile 变量规则
volatile 变量的写,happens-before 对这个变量的读。

🔹 线程启动规则
Thread.start() 之前的操作,对该线程内可见。

🔹 线程终止规则
Thread.join() 之后主线程能看到子线程结果。

🔹 传递性规则
如果 A happens-before B,且 B happens-before C,那么 A happens-before C。


🛠️ 3、常见实践样例

示例 1:程序顺序规则(单线程)

int a = 10;
int b = a + 1;
System.out.println(b);

这里 a = 10 happens-before b = a + 1,因为它们在同一线程中按顺序执行。

示例 2:synchronized 保证可见性

class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++; // 写操作
    }

    public synchronized int getCount() {
        return count; // 读操作
    }
}

synchronized 保证了 increment() 中的写,对 getCount() 是可见的。

示例 3:volatile 变量的可见性

class FlagDemo {
    private volatile boolean flag = false;

    public void writer() {
        flag = true; // 写
    }

    public void reader() {
        while (!flag) {
            // 循环等待直到 flag 为 true
        }
        System.out.println("Flag changed!");
    }
}

使用 volatile 保证 writer() 的写操作对 reader() 是可见的。

示例 4:Thread.start() 之前的 happens-before

public class StartExample {
    static int value;

    public static void main(String[] args) throws Exception {
        value = 42; // 写操作

        Thread t = new Thread(() -> {
            System.out.println(value); // 能看到 value = 42
        });

        t.start(); // happens-before 线程内读取
    }
}

示例 5:Thread.join() 之后的可见性

public class JoinExample {
    static int result = 0;

    public static void main(String[] args) throws Exception {
        Thread t = new Thread(() -> {
            result = 100;
        });

        t.start();
        t.join(); // 主线程等待 t 执行完成

        System.out.println(result); // 一定是 100
    }
}

⚠️ 4、如果没有 happens-before,会发生什么?

示例:没有 volatile 的读写

class NoVisibility {
    private boolean ready = false;

    public void writer() {
        ready = true;
    }

    public void reader() {
        while (!ready) {
            // 无限等待
        }
        System.out.println("Ready is true");
    }
}

这里 reader() 可能永远看不到 ready = true,因为没有构建 happens-before 关系。


🧠 5、总结

🔹 使用 volatile 标记简单状态变量,保证线程间通信可见性。
🔹 对于复杂共享变量使用 synchronizedLock
🔹 明确场景构建 happens-before 保证,避免“看不到”的问题。
🔹 如果你使用 Java 并发包(如 AtomicIntegerCountDownLatch),它们内部都已封装了 happens-before。

规则 示例 应用场景
程序顺序 同线程代码顺序 基本逻辑
synchronized 锁写 -> 锁读 并发安全类
volatile 写 volatile -> 读 volatile 状态标识
Thread.start 启动前变量 -> 线程中可见 线程初始化
Thread.join 线程结束 -> 主线程可见 子线程计算值
动物装饰