happens-before规则详解

166 阅读3分钟

一、程序顺序规则(单线程顺序性)

规则

同一线程中的操作,按代码顺序执行(但不禁止编译器和 CPU 的指令重排序,只要结果一致)。

示例

int a = 1;     // 操作1
int b = 2;     // 操作2
int c = a + b; // 操作3
  • happens-before 关系
    操作1 → 操作2 → 操作3(尽管实际执行时操作1和2可能重排序,但结果必须保证 c=3)。

二、volatile 变量规则

规则

volatile 变量的写操作 happens-before 后续的读操作,表示存在数据依赖时的可见性和有序性。

示例

public class VolatileExample {
    private volatile boolean flag = false;
    private int value = 0;

    public void writer() {
        value = 42;  // 操作1
        flag = true; // 操作2(volatile 写)
    }

    public void reader() {
        if (flag) {           // 操作3(volatile 读)
            System.out.println(value); // 输出 42
        }
    }
}
  • happens-before 关系
    操作1 → 操作2(程序顺序规则)
    操作2 → 操作3(volatile 规则)
    操作3 → 操作4(程序顺序规则)
  • 结果保证:线程 B 在读到 flag=true 时,一定能看到 value=42

三、锁规则(synchronized

规则

解锁操作 happens-before 后续的加锁操作。

示例

public class LockExample {
    private int count = 0;
    private final Object lock = new Object();

    public void increment() {
        synchronized (lock) { // 加锁
            count++;          // 操作1
        }                     // 解锁
    }

    public int getCount() {
        synchronized (lock) { // 加锁
            return count;     // 操作2
        }                     // 解锁
    }
}
  • happens-before 关系
    线程 A 的解锁 → 线程 B 的加锁
  • 结果保证:线程 B 调用 getCount() 时,能看到线程 A 对 count 的修改。

四、线程启动规则

规则

Thread.start() 前的操作 happens-before 新线程中的所有操作。

示例

public class ThreadStartExample {
    static int data = 0;

    public static void main(String[] args) {
        data = 100;                   // 操作1
        Thread thread = new Thread(() -> {
            System.out.println(data); // 输出 100(操作2)
        });
        thread.start();               // 操作3
    }
}
  • happens-before 关系
    操作1 → 操作3 → 操作2
  • 结果保证:新线程能看到 data=100

五、线程终止规则

规则

线程中的所有操作 happens-before 其他线程检测到该线程终止(如 Thread.join())。

示例

public class ThreadJoinExample {
    static int result = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            result = 42; // 操作1
        });
        thread.start();  // 操作2
        thread.join();   // 操作3
        System.out.println(result); // 输出 42(操作4)
    }
}
  • happens-before 关系
    操作1 → 操作3 → 操作4
  • 结果保证:主线程在 join() 后能看到 result=42

六、传递性规则

规则

若 A happens-before B,且 B happens-before C,则 A happens-before C。

示例

public class TransitivityExample {
    private int x = 0;
    private volatile boolean flag = false;

    public void writer() {
        x = 1;          // 操作1
        flag = true;    // 操作2(volatile 写)
    }

    public void reader() {
        if (flag) {     // 操作3(volatile 读)
            System.out.println(x); // 输出 1(操作4)
        }
    }
}
  • happens-before 关系
    操作1 → 操作2(程序顺序规则)
    操作2 → 操作3(volatile 规则)
    操作3 → 操作4(程序顺序规则)
    因此,操作1 → 操作4(传递性)
  • 结果保证:线程 B 看到 flag=true 时,一定能看到 x=1

七、happens-before 的实际意义

  1. 简化多线程编程:开发者无需关心底层指令重排序和缓存细节,只需遵循 happens-before 规则。
  2. 指导同步机制选择
    • 使用 volatile 保证可见性和有序性。
    • 使用锁(synchronizedLock)保证原子性和复合操作的顺序性。
  3. 避免数据竞争:通过规则组合,确保多线程访问共享资源的安全。

总结

规则典型场景保证内容
程序顺序规则单线程代码顺序单线程执行结果正确
volatile 规则状态标志、双重检查锁可见性、禁止重排序
锁规则同步代码块互斥访问、可见性
线程启动/终止规则Thread.start()/Thread.join()跨线程操作可见性
传递性规则多规则组合跨操作链的可见性和有序性

理解 happens-before 是编写正确并发程序的关键!