不遵守 happens-before 规则最直接的后果是:多线程下共享变量的读写操作失去顺序保证和可见性保障,导致程序行为不可预测——如读到过期值、状态不一致、死循环或空指针,这些是JMM允许的合法重排结果。

不遵守 happens-before 规则,最直接的后果是:**多线程下共享变量的读写操作失去顺序保证和可见性保障,导致程序行为不可预测——比如读到过期值、逻辑跳过、状态不一致,甚至死循环或空指针。** 这些问题不是偶发 bug,而是 JMM 允许的合法重排结果。
volatile 未修饰时的写-读乱序
当一个普通变量被多个线程读写,且没有 happens-before 约束,JVM 和 CPU 可能将写操作“推后”、读操作“提前”,只要单线程语义不变(as-if-serial)。例如:
-
线程 A:
this.flag = true;(想作为“完成信号”)this.data = 42; -
线程 B:
while (!flag) {}System.out.println(data);
若 flag 和 data 都是非 volatile 变量,编译器或 CPU 可能重排为:
→ A 中先执行 flag = true,再执行 data = 42;
→ 但 B 可能先看到 flag == true,却读到 data == 0(初始值),因为 data 的写尚未刷入主内存,或 B 从本地缓存读到了旧值。
锁未正确配对导致的可见性断裂
监视器锁规则要求“解锁 happens-before 后续加锁”。如果锁对象不一致、加锁范围遗漏、或用不同锁保护同一变量,就会破坏该链路:
立即学习“Java免费学习笔记(深入)”;
- A 线程在
synchronized(lock1)中修改count - B 线程在
synchronized(lock2)中读取count
此时两个操作之间无 happens-before 关系,B 完全可能读到 stale 值。即使 A 已执行完并释放 lock1,B 的 lock2 不构成同步点,JMM 不保证其看到 A 的写结果。
线程启动/终止未等待引发的竞态
线程启动规则规定 start() happens-before 线程内任意操作;终止规则规定线程内所有操作 happens-before join() 返回。若忽略这些:
- 主线程修改共享变量后直接
t.start(),但没确保修改发生在start()之前 → 新线程可能看不到初始值 - 子线程更新状态后退出,主线程未调用
t.join()就去读该状态 → 可能读到未更新的旧值,因无 happens-before 保证
传递性断裂造成隐性失效
happens-before 具有传递性:A → B 且 B → C,则 A → C。一旦中间环节缺失,整个链就断了。典型例子是“双重检查锁定”中漏掉 volatile:
if (instance == null) { synchronized(Lock) { if (instance == null) instance = new Singleton(); } }- 若
instance非 volatile,构造函数内的字段赋值(如field = value)可能被重排到instance引用赋值之后 - 另一个线程看到
instance != null,但其中字段仍是默认值(如null或0)
根本原因:new 对象的三步(分配内存、初始化、引用赋值)中,后两步无 happens-before 保证,导致其他线程“看到部分构造的对象”。


















