核心概念

happens-before原则 是Java内存模型(JMM)中的核心规则,用于定义两个操作之间的内存可见性关系。如果操作A happens-before 操作B,那么A的执行结果对B可见,且A的执行顺序在B之前。

简单来说:前一个操作的结果对后续操作可见


核心规则

JMM定义了以下8条happens-before规则:

1. 程序顺序规则(Program Order Rule)

在一个线程内,按照代码顺序,前面的操作happens-before后面的操作。

int a = 1;  // 操作1
int b = 2;  // 操作2
int c = a + b;  // 操作3
// 操作1 happens-before 操作2 happens-before 操作3

注意:这不意味着前面的操作一定先执行,但前面操作的结果对后面操作可见。

2. 监视器锁规则(Monitor Lock Rule)

对一个锁的unlock操作 happens-before 后续对这个锁的lock操作。

public class LockExample {
    private int value = 0;
    private final Object lock = new Object();
    
    public void writer() {
        synchronized (lock) {
            value = 1;  // 操作1
        } // unlock happens-before 后续的lock
    }
    
    public void reader() {
        synchronized (lock) {  // lock
            int temp = value;  // 操作2:能看到value=1
        }
    }
}

3. volatile变量规则(Volatile Variable Rule)

对一个volatile变量的写操作 happens-before 后续对这个变量的读操作。

public class VolatileExample {
    private volatile boolean flag = false;
    private int value = 0;
    
    public void writer() {
        value = 1;           // 操作1
        flag = true;         // 操作2:volatile写
    }
    
    public void reader() {
        if (flag) {          // 操作3:volatile读
            int temp = value;  // 操作4:能看到value=1
        }
    }
}
// 操作2 happens-before 操作3,结合程序顺序规则,操作1的结果对操作4可见

4. 线程启动规则(Thread Start Rule)

线程的start()方法 happens-before 该线程的所有操作。

int x = 0;
Thread t = new Thread(() -> {
    int y = x;  // 能看到x=10
});
x = 10;  // 操作1
t.start();  // 操作2 happens-before 线程内的操作

5. 线程终止规则(Thread Termination Rule)

线程的所有操作 happens-before 其他线程检测到该线程终止(通过join()或isAlive())。

int x = 0;
Thread t = new Thread(() -> {
    x = 10;  // 操作1
});
t.start();
t.join();  // 等待线程结束
int y = x;  // 能看到x=10

6. 线程中断规则(Thread Interruption Rule)

对线程interrupt()方法的调用 happens-before 被中断线程检测到中断事件的发生。

Thread t = new Thread(() -> {
    while (!Thread.interrupted()) {
        // 能检测到中断
    }
});
t.start();
t.interrupt();  // happens-before 线程内的interrupted()检测

7. 对象终结规则(Finalizer Rule)

对象的构造函数完成 happens-before 该对象的finalize()方法开始。

public class FinalizeExample {
    private int value;
    
    public FinalizeExample() {
        value = 10;  // happens-before finalize()
    }
    
    @Override
    protected void finalize() {
        int temp = value;  // 能看到value=10
    }
}

8. 传递性规则(Transitivity)

如果A happens-before B,B happens-before C,那么A happens-before C。

private volatile boolean flag = false;
private int value = 0;

// 线程1
value = 1;      // A
flag = true;    // B (volatile写)

// 线程2
if (flag) {     // C (volatile读)
    int x = value;  // D,能看到value=1
}
// A happens-before B (程序顺序规则)
// B happens-before C (volatile规则)
// 因此 A happens-before C,再结合程序顺序规则,A happens-before D

原理分析

1. 与内存屏障的关系

happens-before规则在底层通过内存屏障(Memory Barrier) 实现:

  • LoadLoad屏障:禁止读操作重排序
  • StoreStore屏障:禁止写操作重排序
  • LoadStore屏障:禁止读后写重排序
  • StoreLoad屏障:禁止写后读重排序(开销最大)
// volatile写操作会插入屏障
value = 1;
// StoreStore屏障
flag = true;  // volatile写
// StoreLoad屏障

// volatile读操作会插入屏障
if (flag) {  // volatile读
    // LoadLoad屏障
    // LoadStore屏障
    int x = value;
}

2. 重排序限制

happens-before规则限制了编译器和处理器的重排序行为:

  • as-if-serial:在单线程中,重排序不能改变程序结果
  • happens-before:在多线程中,保证必要的可见性和有序性

并发场景实践

1. DCL单例模式

public class Singleton {
    // 必须使用volatile,防止指令重排序
    private static volatile Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {  // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) {  // 第二次检查
                    // new操作分三步:
                    // 1. 分配内存
                    // 2. 初始化对象
                    // 3. 引用指向内存
                    // volatile防止2和3重排序
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

2. 生产者-消费者模式

public class ProducerConsumer {
    private volatile boolean hasData = false;
    private int data = 0;
    
    // 生产者
    public void produce(int value) {
        data = value;        // 操作1
        hasData = true;      // 操作2:volatile写
    }
    
    // 消费者
    public int consume() {
        while (!hasData) {   // volatile读
            Thread.yield();
        }
        return data;  // 能看到最新的data值
    }
}

3. 线程安全的延迟初始化

public class LazyInitialization {
    private volatile Helper helper;
    
    public Helper getHelper() {
        if (helper == null) {
            synchronized (this) {
                if (helper == null) {
                    // volatile保证初始化的完整性对其他线程可见
                    helper = new Helper();
                }
            }
        }
        return helper;
    }
}

面试总结

核心要点

  1. happens-before是可见性保证,不是时间上的先后顺序
  2. 8大规则:程序顺序、锁、volatile、线程启动/终止/中断、对象终结、传递性
  3. 通过内存屏障实现:禁止特定的指令重排序
  4. 实际应用:DCL单例、线程通信、volatile变量等场景

答题模板

面试时可以这样回答:

  1. 定义:happens-before是JMM的核心规则,定义操作间的可见性关系
  2. 作用:如果A happens-before B,则A的结果对B可见,A在B之前
  3. 规则:列举2-3个常用规则(如volatile、锁、线程启动)
  4. 实践:举例说明(如DCL单例中volatile的作用)
  5. 原理:通过内存屏障限制重排序,保证多线程正确性

常见误区

// ❌ 错误理解:happens-before = 时间先后
int a = 1;
int b = 2;
// a=1不一定在时间上先于b=2执行,但a的结果对后续操作可见

// ✅ 正确理解:happens-before = 可见性保证
volatile boolean flag = false;
int value = 0;
// 写线程
value = 1;
flag = true;  // volatile写
// 读线程
if (flag) {
    int x = value;  // 一定能看到value=1
}