什么是 Java 的 happens-before 规则?
Java 的 Happens-Before 规则
Happens-Before是 Java 内存模型(JMM)中用于描述操作之间可见性和有序性的核心概念。它定义了一套规则,确保一个操作的结果对另一个操作是可见的。
核心概念
Happens-Before 关系:如果操作 Ahappens-before操作 B,那么:
- 操作 A 的执行结果对操作 B 可见
- 操作 A 的执行顺序在操作 B 之前
八大 Happens-Before 规则
1. 程序次序规则
单线程内,按照代码顺序,书写在前面的操作 happens-before 书写在后面的操作。
publicvoidmethod(){inta=1;// 1intb=2;// 2intc=a+b;// 3// 1 happens-before 2// 2 happens-before 3// 1 happens-before 3(传递性)}2. 管程锁定规则
一个unlock操作 happens-before 后续对同一个锁的lock操作。
classSharedResource{privateintcount=0;// 线程 A 执行publicvoidincrement(){synchronized(this){count++;// unlock happens-before 下一次 lock}}// 线程 B 执行publicintgetCount(){synchronized(this){returncount;// 能看到线程 A 的修改}}}3. volatile 变量规则
对一个volatile变量的写操作happens-before 后续对这个变量的读操作。
classVolatileExample{privatevolatilebooleanflag=false;// 线程 Apublicvoidwriter(){flag=true;// volatile 写}// 线程 Bpublicvoidreader(){if(flag){// volatile 读,能看到线程 A 的写入// 执行某些操作}}}4. 线程启动规则
Thread.start()方法 happens-before 该线程的每一个动作。
publicclassStartRule{privateintx=0;publicstaticvoidmain(String[]args){StartRuleexample=newStartRule();example.x=10;// 主线程设置Threadthread=newThread(()->{// 能看到 x = 10System.out.println(example.x);});thread.start();// start() happens-before 线程执行}}5. 线程终止规则
线程中的所有操作都 happens-before 其他线程从该线程的join()方法成功返回。
publicclassJoinRule{privateintresult=0;publicstaticvoidmain(String[]args)throwsInterruptedException{JoinRuleexample=newJoinRule();Threadthread=newThread(()->{example.result=42;// 线程内操作});thread.start();thread.join();// join() 返回 happens-before 后续操作// 能看到 result = 42System.out.println(example.result);}}6. 线程中断规则
对线程interrupt()方法的调用 happens-before 被中断线程的代码检测到中断事件。
publicclassInterruptRule{publicstaticvoidmain(String[]args){Threadthread=newThread(()->{while(!Thread.currentThread().isInterrupted()){// 工作中...}// 能检测到中断信号System.out.println("Thread interrupted");});thread.start();thread.interrupt();// interrupt() happens-before 检测到中断}}7. 对象终结规则
一个对象的初始化完成(构造函数执行结束)happens-before 它的finalize()方法的开始。
publicclassFinalizeRule{privateintvalue;publicFinalizeRule(intvalue){this.value=value;// 构造函数执行}@Overrideprotectedvoidfinalize()throwsThrowable{// 能看到构造函数中设置的 valueSystem.out.println("Finalize: "+value);super.finalize();}}8. 传递性规则
如果操作 A happens-before 操作 B,且操作 B happens-before 操作 C,那么操作 A happens-before 操作 C。
classTransitivityRule{privatevolatileintx=0;privateinty=0;// 线程 ApublicvoidthreadA(){x=1;// 1y=2;// 2}// 线程 BpublicvoidthreadB(){intr1=y;// 3intr2=x;// 4}}分析:
- 如果
y是volatile,则2 happens-before 3 - 根据程序次序规则,
1 happens-before 2 - 根据传递性,
1 happens-before 3 - 因此线程 B 能看到
x = 1
实际应用示例
示例1:双重检查锁
publicclassSingleton{privatestaticvolatileSingletoninstance;// volatile 关键publicstaticSingletongetInstance(){if(instance==null){// 第一次检查synchronized(Singleton.class){if(instance==null){// 第二次检查instance=newSingleton();// new Singleton() 包含:// 1. 分配内存// 2. 初始化对象// 3. 将引用指向内存// volatile 禁止 2 和 3 重排序}}}returninstance;}}happens-before 分析:
- volatile 写 happens-before volatile 读
- 保证其他线程能看到完全初始化的对象
示例2:生产者-消费者
classProducerConsumer{privatevolatilebooleanhasData=false;privateintdata;// 生产者publicvoidproduce(intvalue){data=value;// 1hasData=true;// 2 - volatile 写}// 消费者publicintconsume(){while(!hasData){// 3 - volatile 读// 等待}returndata;// 4 - 能看到 1 的结果}}happens-before 分析:
2 happens-before 3(volatile 规则)1 happens-before 2(程序次序规则)3 happens-before 4(程序次序规则)- 传递性:
1 happens-before 4 - 因此消费者能看到生产者设置的 data
总结
| 规则 | 关键点 | 应用场景 |
|---|---|---|
| 程序次序 | 单线程顺序 | 保证单线程内执行顺序 |
| 管程锁定 | unlock → lock | synchronized 块间可见性 |
| volatile | 写 → 读 | 状态标志、单例模式 |
| 线程启动 | start() → 线程动作 | 线程初始化数据传递 |
| 线程终止 | 线程动作 → join() | 获取线程执行结果 |
| 线程中断 | interrupt() → 检测中断 | 优雅停止线程 |
| 对象终结 | 构造完成 → finalize() | 对象清理 |
| 传递性 | A→B, B→C ⇒ A→C | 复合场景分析 |
核心思想:happens-before 规则不需要所有操作都按顺序执行,只需要保证必要的顺序关系,从而在保证正确性的前提下,允许编译器和处理器进行优化重排序。
