Java 内存模型与 happens-before

JMM 与 happens-before:何时一次写入对其它线程可见,同步动作如何建立顺序。

#type / concept #status / growing #tech / dev #resource / java

[!info] 关联笔记

Java 内存模型与 happens-before

这个概念为什么出现

并发 bug 常不是“没想到交错”,而是没有同步却假设看见最新值。Java 内存模型(JMM)定义:在何种同步关系下,线程 A 的写对线程 B 的读可见且有序。核心工具概念是 happens-before

[!abstract] 一句话理解 若操作 A happens-before 操作 B,则 A 的结果对 B 可见且有序;锁释放对后续加锁、volatile 写对后续读、线程 start/join 等都会建立 happens-before。

最小可观察示例

场景:发布不可变配置快照

public class ConfigPublish {
    static class Cfg {
        final int port;
        Cfg(int port) { this.port = port; }
    }
    private volatile Cfg ready; // 用 volatile 发布引用

    void publish(int port) {
        Cfg c = new Cfg(port); // 先构造完成
        ready = c;             // 发布:写 volatile
    }

    int readPort() {
        Cfg c = ready;         // 读 volatile
        return c == null ? -1 : c.port;
    }

    public static void main(String[] args) throws Exception {
        ConfigPublish p = new ConfigPublish();
        Thread writer = new Thread(() -> p.publish(8080));
        writer.start();
        writer.join(); // join 建立 happens-before
        System.out.println(p.readPort());
    }
}

建议运行:

javac ConfigPublish.java && java ConfigPublish

期望输出:

8080

结合场景再看三个关注点

  1. 安全发布对象构造完成后再发布引用。
  2. volatile/join/锁是规范层工具,不是“睡眠碰运气”。
  3. 没有同步的共享可变属于数据竞争,行为可极难推理。

核心概念与准确模型

1. happens-before 常见来源(直觉列表)

  • 程序次序(单线程内)
  • 监视器锁:unlock → 后续 lock 同一锁
  • volatile:写 → 后续读同一变量
  • 线程 start:start → 新线程中的动作
  • 线程 join:线程结束 → join 返回后
  • 传递性

2. 数据竞争

无 happens-before 关系的冲突读写 = 数据竞争;正确程序应避免。

3. 规范 vs 硬件

  • 用规范推理正确性
  • CPU/缓存是实现与直觉辅助,不能替代规范

边界情况与反直觉行为

  1. 双重检查锁定必须正确使用 volatile(经典题)。
  2. final 字段安全发布有专门规则。
  3. 单测“偶尔通过”不能证明无数据竞争。

常见误区

[!warning] 常见误区:加几个 sleep 让并发测试变绿 那是掩盖竞态;应建立同步与用并发测试工具。

工程实践

  1. 优先不可变 + 消息传递缩小共享。
  2. 共享可变必须能指出 happens-before 边。
  3. 用 jdk.jfr / 线程转储 / 并发 TLA 级推理(进阶)。
  4. 读 JCIP 与 JLS/JMM 章节交叉验证。

本节总结

  • JMM 回答可见性与有序
  • happens-before 是推理货币
  • 无同步共享可变即风险

自测题

  1. 为什么 unlock 对后续 lock 重要?
  2. join 提供什么保证?
参考答案
  1. 释放锁 happens-before 后续获取同一锁,从而刷新临界区写入。
  2. 被 join 线程中的动作 happens-before join 返回后的动作。

延伸阅读与资料来源

资料类型支撑内容
JLS Chapter 17 Threads and Locks规范JMM
JSR 133 FAQ经典 FAQ直觉问答
创建于 2026/7/15 更新于 2026/7/15