happens-before 规则是 jvm 对代码执行顺序的最小承诺,仅当操作满足其规则时才保证前序结果对后续操作可见;它涵盖 volatile 读写、synchronized 锁释放与获取、thread.start/join、final 字段初始化等场景,组合使用需逐条验证链式完整性。

Happens-Before 规则不是“内存可见性”的说明书,而是 JVM 对代码执行顺序能做出的最小承诺——你写的代码,只有满足其中一条,JVM 才保证前一个操作的结果对后一个操作可见。
volatile 写与读之间为什么能建立 happens-before
写 volatile 变量和后续读该变量的操作之间,自动构成 happens-before 关系。这不是靠“加锁”或“刷新缓存”这种实现细节,而是语言规范强制要求:编译器不能重排序、运行时不能乱优化、CPU 不能绕过内存屏障。
- 常见错误现象:
flag = true(非 volatile)之后启动线程,另一线程看到flag仍为 false,即使已执行很久 - 正确做法:把
flag声明为volatile boolean flag,才能让写flag和读flag构成 happens-before - 注意:仅对同一变量有效;
volatile int x的写不能保证对y的写也可见
synchronized 块的解锁与加锁如何链式传递可见性
一个线程对某把锁的 unlock 操作,happens-before 另一个线程对该锁的 lock 操作。这意味着:前一线程在临界区内写的所有变量,后一线程进入临界区后都能看到最新值。
- 使用场景:多线程协作中,用同一把锁保护多个状态变量(如
count和status),不必每个都加 volatile - 容易踩的坑:用不同对象作锁(
new Object()每次新建),无法建立锁之间的 happens-before 链 - 性能影响:synchronized 的 happens-before 成本主要在锁竞争,而非规则本身;无竞争时开销极小
Thread.start() 和 Thread.join() 怎么参与排序约束
调用 thread.start() happens-before 该线程的任何动作;该线程所有动作又 happens-before 其他线程调用 thread.join() 返回。
立即学习“Java免费学习笔记(深入)”;
- 典型误用:主线程启动子线程后直接读共享变量,没等
join()或同步手段,结果读到初始值 - 正确姿势:要么在子线程里写完后设 volatile 标志,要么用
join()等待结束——后者天然带全套 happens-before 保证 - 参数差异:
join(1000)超时返回不等于线程结束,此时不能假设 happens-before 已生效
构造函数完成与 final 字段的可见性陷阱
对象构造完毕(即构造函数最后一行执行完)happens-before 该对象引用被发布(如赋值给静态变量、放入集合、传给其他线程)。但前提是:字段是 final,且构造过程中没有 this 逸出。
- 严重错误现象:另一个线程拿到对象引用,读
final int x却是 0(未初始化值),通常是因为构造函数里把this传给了其他线程或监听器 - 关键条件:必须是
final字段;普通字段即使构造中赋值,也不受此规则保护 - 兼容性注意:Java 5+ 才正式确立该语义;老版本 JVM 可能不遵守
真正难的是组合使用——比如 volatile 写 + synchronized 读 + join 等待混在一起时,happens-before 链是否完整,得逐条对照规则检查。漏掉任意一环,JVM 就有权当它不存在。










