判断多线程中变量修改是否可见,关键在于Happens-Before关系:存在则保证可见,否则可能读到旧值;程序顺序、锁、volatile、启动、终止五条规则覆盖绝大多数场景。

判断多线程中一个线程对变量的修改能否被另一个线程看到,关键不是看代码写在前面还是后面,而是看两个操作之间是否存在 Happens-Before 关系。只要存在,JMM 就保证写操作的结果对读操作可见;不存在,则可能读到旧值、0 或未定义值——哪怕逻辑上“应该”看到。
程序顺序规则:单线程内的可见性基础
同一线程中,按控制流顺序(非单纯代码行号,含 if/for 等分支结构),前一个操作 happens-before 后一个操作。这是所有规则的起点。
- 它不禁止指令重排序,但要求重排序后结果与顺序执行一致(as-if-serial 语义)
- 只保障本线程内可见性,对其他线程无约束
- 例如:
int a = 1; int b = a + 2;,a 赋值一定 happens-before b 计算,b 必然看到 a=1
锁规则与 volatile 规则:跨线程传递可见性的核心手段
这两个是最常用、最直接建立跨线程 happens-before 的方式。
-
锁规则:对某把锁的
unlock操作 happens-before 后续对该锁的lock操作。注意“后续”指时间上发生在 unlock 之后,无论是否同一线程 - volatile 规则:对 volatile 变量的写 happens-before 后续对该变量的读。写操作会插入写屏障,读操作插入读屏障,阻止重排序并强制刷写/加载主内存
- 两者都要求“同一个锁”或“同一个 volatile 变量”,混用无效
线程启动、终止与中断规则:生命周期中的可见性保障
这些规则把线程创建、结束、中断等关键节点变成 happens-before 的锚点。
-
启动规则:主线程调用
t.start()的操作 happens-before 子线程中任意操作。子线程能看见 start() 前主线程对共享变量的所有修改 -
终止规则:子线程所有操作 happens-before 主线程调用
t.join()返回。主线程在 join 后能安全读取子线程写入的变量 -
中断规则:主线程调用
t.interrupt()happens-before 子线程检测到中断(如Thread.interrupted()返回 true)
传递性与对象终结规则:补全逻辑链条
传递性让多个 happens-before 关系可以串起来,是构建复杂同步逻辑的基础。
- 若 A → B 且 B → C,则 A → C。例如:线程 A 写 volatile flag,线程 B 读 flag 后加锁,线程 C 后续获取同一把锁,则 A 的写对 C 可见
-
对象终结规则:构造函数执行完毕 happens-before
finalize()方法开始。主要用于资源清理场景,日常开发较少直接依赖 - 八条规则中,前五条(程序顺序、锁、volatile、启动、终止)覆盖了绝大多数实际需求

















