Java happens-before规则完全指南:从偏序关系到内存可见性 Java happens-before规则完全指南从偏序关系到内存可见性一、 什么是happens-before1.1 从可见性问题说起1.2 为什么需要happens-before1.3 happens-before vs 时间先后二、 流程图happens-before的八条规则全景三、 八条happens-before规则详解3.1 规则一程序次序规则Program Order Rule3.2 规则二锁规则Monitor Lock Rule3.3 规则三volatile变量规则Volatile Variable Rule3.4 规则四线程启动规则Thread Start Rule3.5 规则五线程终止规则Thread Termination Rule3.6 规则六传递性规则Transitivity Rule3.7 ⚪ 规则七线程中断规则3.8 规则八对象终结规则四、 流程图happens-before保证可见性的完整链路五、 happens-before的实战价值5.1 判断数据竞争与线程安全5.2 实现同步的技巧借助规则组合5.3 常见误区六、 总结The Begin点点关注收藏不迷路⬇ ⬇ 底部 ⬇ ⬇本文导读本文将系统讲解Java内存模型中的核心概念——happens-before规则从定义理解到八条规则详解从代码示例到实际应用场景用流程图帮助你彻底掌握这个判断数据竞争和线程安全的核心依据。全文包含四大核心章节、3个彩色流程图、6个代码示例。预计阅读时间25分钟。一、 什么是happens-before1.1 从可见性问题说起在多线程编程中一个线程修改了共享变量的值另一个线程可能永远看不到这个修改。这是因为CPU缓存和指令重排序的存在——修改可能停留在缓存中指令可能被重新排列。**Java内存模型JMM**定义了happens-before规则来解决这个问题。一句话理解happens-before是程序中两个动作之间的一种关系——如果动作A happens-before动作B那么A所做的所有更改在B执行时一定可见。它不是简单的“时间先后”而是“可见性保证”。1.2 为什么需要happens-before在没有happens-before保证的情况下编译器和处理器可以对指令进行重排序导致看似“不可能发生”的结果。// 初始值a 0, b 0// 线程1执行a10;// 操作1r1b;// 操作2// 线程2执行b20;// 操作3r2a;// 操作4由于操作1和操作2之间没有数据依赖编译器可能将它们重排序。结果可能是r1 20看到了未来的写、r2 0没看到a的写或更离奇的结果。happens-before的作用定义哪些情况下重排序是被禁止的从而保证多线程程序的正确性。1.3 happens-before vs 时间先后happens-before不单纯是时间顺序它是内存可见性的保证。关系含义时间先后事件A在时钟上先于B发生happens-before事件A的结果对事件B可见且A的执行顺序优先于B如果两个操作之间没有happens-before关系JVM可以自由地对它们进行重排序数据竞争就有可能发生。二、 流程图happens-before的八条规则全景 happens-before规则 程序次序规则 锁规则 volatile规则 线程启动规则 线程终止规则 传递性规则⚪ 线程中断规则 对象终结规则同一线程内前面的操作 → 后面的操作解锁 → 后续对该锁的加锁volatile写 → 后续对该变量的读start 线程 → 该线程所有操作线程所有操作 → join返回A→B 且 B→C ⇒ A→Cinterrupt调用 → 检测到中断构造完成 → finalize开始三、 八条happens-before规则详解3.1 规则一程序次序规则Program Order Rule在同一线程内按照代码顺序书写在前面的操作happens-before书写在后面的操作。// 同一线程内代码顺序保证inta1;// 操作1intb2;// 操作2intcab;// 操作3// 操作1 happens-before 操作2// 操作2 happens-before 操作3// 操作1 happens-before 操作3传递性这条规则保证了单线程内的语义不变性as-if-serial。3.2 规则二锁规则Monitor Lock Rule对一个锁的解锁操作happens-before后续对该锁的加锁操作。// 锁规则保证可见性synchronized(lock){sharedVar42;// 写操作}// 解锁// ...synchronized(lock){System.out.println(sharedVar);// 加锁后保证看到42}关键点线程A在释放锁前对共享变量的所有修改在线程B随后获取同一把锁时一定可见。3.3 规则三volatile变量规则Volatile Variable Rule对volatile变量的写操作happens-before后续对该变量的读操作。// volatile提供可见性保证volatilebooleanreadyfalse;intdata0;// 线程1data123;// 普通写readytrue;// volatile写// 线程2if(ready){// volatile读System.out.println(data);// 保证看到data123}重要机制volatile写不仅仅是让自身立即刷新到主内存还能守护其上下文中的普通变量使其对后续读线程可见。3.4 规则四线程启动规则Thread Start Rule调用Thread.start() happens-before该线程开始执行的所有操作。// 启动规则ThreadworkernewThread(()-{System.out.println(data);// 保证能看到主线程的修改});data100;// 普通写worker.start();// start happens-before worker线程所有操作意义主线程在start()之前的所有修改对子线程都是可见的。3.5 规则五线程终止规则Thread Termination Rule线程的所有操作happens-before其他线程从该线程的join()返回。// 终止规则ThreadworkernewThread(()-{resultcompute();// 子线程写});worker.start();worker.join();// join返回System.out.println(result);// 保证看到子线程的所有修改3.6 规则六传递性规则Transitivity Rule如果A happens-before B且B happens-before C则A happens-before C。hbhb传递性 hb操作A操作B操作C传递性的实际威力可以将volatile规则和程序次序规则串联起来实现普通变量的跨线程可见性。3.7 ⚪ 规则七线程中断规则对线程interrupt()的调用happens-before被中断线程检测到中断事件的发生。// 中断规则thread.interrupt();// 调用中断// 被中断线程的代码检测到中断 → 一定能看到中断的调用3.8 规则八对象终结规则一个对象的构造完成happens-before其finalize()方法的开始。四、 流程图happens-before保证可见性的完整链路 线程B 线程Avolatile规则程序次序规则程序次序规则传递性普通变量写 x1volatile变量写 flagtruevolatile变量读 flagtrue普通变量读 看到x1五、 happens-before的实战价值5.1 判断数据竞争与线程安全happens-before是判断数据是否存在竞争、线程是否安全的主要依据。当一个变量被多个线程读取且至少被一个线程写入时如果读操作和写操作之间没有happens-before关系就会产生数据竞争Data Race。正确同步的程序Correctly Synchronized Program是没有数据竞争的程序。5.2 实现同步的技巧借助规则组合通过组合happens-before规则可以对未被锁或volatile保护的普通变量实现可见性保证。// 利用程序次序规则 volatile规则实现普通变量可见性staticintnum0;// 普通变量staticvolatilebooleanflagfalse;// volatile守卫// 线程1num42;// 普通写flagtrue;// volatile写程序次序num写 → flag写// 线程2if(flag){// volatile读System.out.println(num);// 保证看到num42}原理链路线程1中num42happens-beforeflagtrue程序次序规则线程1的flagtruehappens-before 线程2的flag读取volatile规则传递性 ⇒num42happens-before 线程2读取num这就是Doug Lea在JUC中广泛使用的技巧。5.3 常见误区序号误区正解①happens-before就是时间先后它是可见性保证不是简单的时间顺序②没有happens-before就一定重排序不一定重排但JVM允许重排不能依赖执行顺序③所有变量都需要volatile或锁通过happens-before组合可实现对普通变量的可见性保证六、 总结happens-before是Java内存模型的核心规则它定义了多线程程序中操作的可见性边界。如果两个操作之间存在happens-before关系前一个操作的结果对后一个操作必然可见如果没有JVM可以自由重排序数据竞争就可能发生。八条规则速查规则关键关系程序次序同一线程代码顺序锁规则解锁 → 加锁volatile规则volatile写 → volatile读线程启动start() → 线程操作线程终止线程操作 → join()传递性A→B, B→C ⇒ A→C中断规则interrupt() → 检测到中断终结规则构造完成 → finalize()The End点点关注收藏不迷路⬆ ⬆ 顶部 ⬆ ⬆