Java 内存模型(Java Memory Model, JMM)解析

Java 内存模型(Java Memory Model, JMM)深度解析

"Write once, run anywhere" 不仅靠字节码,更靠清晰定义的 Java 内存模型

它回答了:在多线程环境下,一个线程对共享变量的写入何时对另一个线程可见?


目录

  1. 为什么需要 JMM
  2. 关键抽象:主内存 & 工作内存
  3. Happens-Before 规则(HB 规则)
  4. 内存屏障与指令重排
  5. volatile、synchronized、final 的 JMM 语义
  6. DCL 问题与 volatile 的救场
  7. Java 9+ 的 JMM 演进(VarHandle、Opaque)
  8. 常见误区与调优建议
  9. 一张图速记

1. 为什么需要 JMM

现象 根源 JMM 作用
缓存不一致 CPU 多级缓存 规定可见性协议
指令重排 / 编译器重排 性能优化 用 HB 规则禁止危险重排
原子性无法保证 非原子读写(long/double) 提供锁或 CAS 语义

没有 JMM,所有并发行为都 "由 JVM 实现细节决定",程序不可移植。


2. 关键抽象:主内存 & 工作内存

text 复制代码
           ┌─────────────┐
           │  主内存      │  所有线程共享的"唯一真相"
           └──────┬──────┘
                  │
    ┌─────────────┴─────────────┐
    │                           │
┌───▼───┐    ┌───▼───┐    ┌───▼───┐
│工作内存A│    │工作内存B│    │工作内存C│ 每个线程私有
└───┬───┘    └───┬───┘    └───┬───┘
    │            │            │
  CPU0          CPU1         CPU2
  • 工作内存:线程私有的高速缓存/寄存器副本。
  • 主内存:真正的堆上对象字段。
  • 线程对变量的所有操作都必须先复制到工作内存,再刷回主内存。
  • 带来 可见性问题 :A 线程写入 x=1,B 线程看到的仍是 x=0

3. Happens-Before 规则(JSR-133 核心)

HB 关系是 偏序 ;若 A hb B,则 A 的写对 B 可见,且禁止编译器把 B 重排到 A 之前。

  1. 程序次序规则

    单线程内,代码书写顺序 happens-before 其后的操作。

  2. 锁规则

    解锁 hb 随后的加锁。

  3. volatile 规则
    volatilehb 任意后续 volatile 读。

  4. 线程启动规则
    Thread.start() hb 该线程的所有动作。

  5. 线程终止规则

    线程所有动作 hb 任何检测到该线程结束的代码(t.join() 返回)。

  6. 中断规则

    对线程 interrupt() hb 检测到中断抛出 InterruptedException

  7. 终结器规则

    构造函数完成 hb finalize() 开始。

  8. 传递性
    A hb BB hb CA hb C


4. 内存屏障与指令重排

JMM 在底层映射为 CPU 内存屏障(Fence)

屏障类型 作用
LoadLoad 禁止读读重排
StoreStore 禁止写写重排
LoadStore 禁止读写重排
StoreLoad 全能型屏障,成本最高(x86 唯一)

HotSpot 在 volatile 写后插 StoreLoad,在读前插 LoadLoad+LoadStore


5. 关键字语义一览

特性 可见性 原子性 可重排序 典型场景
volatile 单次读/写 状态标志、DCL 单例
synchronized 代码块 临界区、复合操作
final ✅(构造函数完成后) 初始化 不可变对象、安全发布

5.1 volatile 深度

java 复制代码
class Counter {
    volatile int count = 0;
    void inc() { count++; }   // 仅保证可见,不保证原子!
}
  • 读/写操作本身会插入屏障;复合操作需配合 AtomicInteger

5.2 synchronized 的 JMM 视角

java 复制代码
synchronized(lock) {
    // 进入:Load屏障,使工作内存失效
    // 离开:Store屏障,把修改强刷回主内存
}
  • 解锁时强制刷回,加锁时强制失效,天然 互斥+可见

5.3 final 安全发布

java 复制代码
class Immutable {
    final int x;
    public Immutable(int x) { this.x = x; } // 写 final
}
// 正确发布
Immutable ref = new Immutable(42);  // 写 ref
  • 写 final 与写 ref 禁止重排 ;其他线程读 ref.x 一定看到 42。

6. Double-Checked Locking 问题 & 解法

java 复制代码
// 有问题的 DCL
class Singleton {
    private static Singleton instance;
    public static Singleton get() {
        if (instance == null) {          // 第一次检查
            synchronized(Singleton.class) {
                if (instance == null)    // 第二次检查
                    instance = new Singleton(); // 可能重排到写引用之前
            }
        }
        return instance;
    }
}

问题:写对象与写引用可能被重排,导致返回 半初始化对象

解法:把 instance 声明为 volatile,禁用重排。


7. Java 9+ 的演进

  • VarHandle :提供 CAS、内存屏障、原子数组操作,绕过 sun.misc.Unsafe
  • Opaque 模式:仅禁止编译器重排,不做 CPU 屏障,用于极致性能场景。
  • JEP 188:标准化多线程语义测试(jcstress)。

8. 常见误区 & 调优建议

误区/做法 正确理解
使用 volatile 保证复合操作原子 Atomic* 或锁
认为 synchronized 一定慢 偏向锁/轻量级锁常接近无锁
HashMapConcurrentHashMap 就安全 只保证结构安全,复合逻辑仍需同步
滥用 System.gc() 解决内存可见 与 JMM 无关,甚至会引入额外 STW

建议

  1. 尽量用 java.util.concurrent 高级工具 ,不要裸写 wait/notify
  2. 共享可变变量统一用 volatile 或锁;不可变对象最安全。
  3. 压测时打开 -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly 查看屏障指令。

9. 一张图速记

graph TD A[Java Code] --> B(编译器/CPU 重排?) B -->|禁止| C{Happens-Before} C --> D((volatile write
synchronized unlock
Thread.start...)) D --> E(内存屏障
LoadStore/StoreLoad...) E --> F(可见性保证)

结语

JMM 不是 JVM 的实现细节,而是给所有并发程序员的"契约"

写出正确高效的并发代码,关键是让代码满足 HB 关系,而非迷信特定硬件行为。

相关推荐
掘金码甲哥30 分钟前
jwt 这点小秘密,你们肯定都知道!
后端
David爱编程2 小时前
Java 字符串拼接用 +、StringBuilder 还是 StringBuffer?一篇给你终极答案
java·后端
码事漫谈2 小时前
C#文件复制异常深度剖析:解决"未能找到文件"之谜
后端
布列瑟农的星空2 小时前
34岁老前端的一周学习总结(2025/8/15)
前端·后端
_風箏2 小时前
Zabbix【问题 01】安装问题 (比 zabbix-release-5.0-1.el7.noarch 还要新) 问题处理
后端
卓码软件测评3 小时前
网站测评-利用缓存机制实现XSS的分步测试方法
java·后端·安全·spring·可用性测试·软件需求
星星电灯猴3 小时前
一次真实的 TF 上架协作案例,从证书到分发的多工具配合流程
后端
Cosolar3 小时前
玩转 WSL:Windows 一键开启 Linux 子系统,轻松实现 SSH 远程连接
后端
rannn_1113 小时前
【Linux学习|黑马笔记|Day4】IP地址、主机名、网络请求、下载、端口、进程管理、主机状态监控、环境变量、文件的上传和下载、压缩和解压
linux·笔记·后端·学习