Skip to content

Java 内存模型如何用 happens-before 解释可见性、有序性和安全发布?

题型:深入 · 区分程序顺序、执行重排和跨线程可观察结果。

建议回答

JMM 定义的是线程之间允许观察到什么结果,而不是要求 CPU 和编译器完全按源码顺序执行。只要不破坏单线程语义和 happens-before 约束,编译器、JIT 和处理器都可以重排。若操作 A happens-before 操作 B,那么 A 的结果对 B 可见,并且在 JMM 的顺序关系上先于 B;没有这条关系的数据竞争程序,可能读到旧值或出现难以直觉解释的组合结果。

常用规则包括:同一线程的程序顺序、监视器解锁先于后续加锁、对 volatile 变量的写先于后续读、线程启动前的操作先于新线程中的操作、线程中的操作先于成功 join 返回,以及规则的传递性。synchronized 同时提供互斥和内存语义;volatile 适合发布状态或构建特定协议,但 count++ 仍是读、改、写的复合操作。

安全发布要求其他线程通过明确的 happens-before 边看到完整初始化后的对象,例如使用静态初始化、锁、volatile 引用、并发容器或在线程启动前完成发布。final 字段还有专门的初始化安全语义,但前提是构造期间没有让 this 逃逸;它也不自动保证字段所引用对象的后续变化安全。

关键权衡

  • 更强的同步边界更容易保证正确性,但可能增加竞争、内存屏障和上下文切换成本。
  • 减少同步可以提升吞吐,却要求状态机、不变量和发布路径足够简单且可证明。
  • Double-checked locking 必须配合 volatile 等正确发布机制,问题不只是“可能读到 null”。

易错点

  • happens-before 是 JMM 的可见性与顺序保证,不等于物理时间上的绝对先后。
  • volatile 不会让所有相关变量自动组成事务,也不能替代跨字段不变量的同步。
  • final 引用不可重新赋值,不代表其指向的集合或对象不可变。

知识关系

从当前问题继续深入,或者回到提出这个问题的知识入口。