JAVA 内存模型(JMM)
并发的本质不是“多线程”,而是“在不确定执行顺序下,仍然保持因果一致性”。
- JMM 解决的根本问题是什么?
- JMM 的最小抽象模型是什么?
- JMM 如何在不可靠的硬件现实之上,构造一个“可被程序员理解的并发世界”?
一、问题定义:为什么需要 Java 内存模型(Why)
1.1 并发的第一性问题
在并发系统中,真正的问题并不是:
- 线程是否同时执行
而是:
- **一个线程对共享状态的修改,是否、何时、以何种顺序,被另一个线程观察到**
这三个问题可抽象为:
可见性(Visibility) + 有序性(Ordering) + 原子性(Atomicity)
如果没有统一约束:
- 不同 CPU 架构
- 不同缓存一致性协议
- 不同编译器优化策略
将导致:
- 同一段 Java 程序,在不同机器上语义不同
1.2 为什么“硬件一致性”不等于“程序一致性”
现代计算机通过 多级缓存 + 指令乱序执行 提升性能:
- 每个 CPU 核心有私有缓存
- 写入并不立即对其他核心可见
- 指令可能被重排序,只要不影响单线程结果
硬件的一致性目标是“最终一致 + 高性能”,而不是:
- 为高级语言提供确定的并发语义
👉 JMM 的使命:
在不可信的硬件与编译器优化之上,为 Java 程序员提供一个确定、可推理的并发语义模型。
二、JMM 的抽象模型(What)
2.1 JMM 的定位
必须首先澄清一个关键误区:
JMM ≠ JVM 内存结构
| 维度 | JMM | JVM 内存结构 |
|---|---|---|
| 性质 | 抽象规范 | 具体实现 |
| 关注点 | 并发语义 | 内存分配 |
| 目标 | 可见性 / 有序性 | 运行效率 |
JMM 是:
JVM 必须遵守的并发语义契约
而不是:
- 堆 / 栈 / 方法区的划分说明
2.2 主内存 / 工作内存:最小并发抽象
JMM 用一个极简但足够表达并发问题的模型描述内存:
主内存(Main Memory):
- 所有线程共享的变量视图
工作内存(Working Memory):
- 线程私有
- 保存主内存变量的副本
关键约束:
- 线程不能直接读写主内存
- 线程间通信 **只能通过主内存完成**
工作内存不是 JVM 的某个内存区域,而是对 CPU 缓存 / 寄存器 / 编译器暂存 的抽象。
2.3 happens-before:JMM 的核心抽象
2.3.1 happens-before 的本质
happens-before 不是时间先后关系,而是:
可见性与有序性的因果约束关系(Partial Order)
含义只有一个:
如果 A happens-before B,那么 A 的结果 对 B 可见,并且 A 的执行顺序 排在 B 之前。
如果两个操作之间 不存在 happens-before 关系:
- JVM 可以自由重排序
- 读取到任意历史值都是合法的
👉 这不是 Bug,而是未定义并发语义。
2.3.2 happens-before 的"天然规则"
这些规则并不是语法糖,而是 并发因果的基本公理:
- 单线程顺序规则
- 锁的释放 → 锁的获取
- volatile 写 → volatile 读
- 线程启动 / 结束 / join
- 中断规则
- 构造完成 → finalize
- 传递性
happens-before 规则集合 = JMM 的最小公理系统
2.4 as-if-serial 语义
JMM 允许大量重排序优化,但有一条不可打破的底线:
在单线程语义下,执行结果必须"看起来像是顺序执行的"
这条规则:
- 约束编译器
- 约束 CPU
- 保证单线程世界的确定性
三、JMM 的并发三特性(模型层)
3.1 原子性
- JMM 保证基本内存操作的原子性
- 对 64 位数据(long / double)允许拆分(非 volatile)
👉 原子性不是"操作不可分",而是:
在并发可观察层面,不可被中途观察到
3.2 可见性
可见性 = 写入结果 是否能被其他线程感知
JMM 提供三种"可见性通道":
- volatile
- synchronized(unlock → 主内存刷新)
- final(构造安全发布)
3.3 有序性
- 单线程:天然有序
- 多线程:除非建立 happens-before,否则无序
指令重排序:
- 不破坏单线程语义
- 但可能破坏并发直觉
四、JMM 的实现手段(How)
JMM 不关心"怎么实现",但 JVM 必须用现实世界的工具去兑现语义承诺。
4.1 volatile:轻量级因果约束
语义本质:
- 建立 **写 → 读 的 happens-before**
- 禁止相关指令重排序
实现层次:
- 字节码:ACC_VOLATILE
- JVM:插入内存屏障
- 硬件:cache flush / fence / lock 指令
适用场景:
- 状态标志
- 单写多读
- 不依赖复合不变式
4.2 synchronized:强因果封闭区
语义本质:
- 构造 **临界区的全序执行视图**
实现层次:
- 字节码:monitorenter / monitorexit
- JVM:对象监视器
- OS / CPU:原子指令
价值:
- 不只是互斥,而是 **内存语义的边界封装**
4.3 内存屏障:语义兑现工具
内存屏障不是给程序员用的,而是:
- JVM 对硬件下达的"秩序约束指令"
屏障类型:
- LoadLoad
- StoreStore
- LoadStore
- StoreLoad
五、工程视角:如何"用对"JMM
5.1 用 happens-before 思考并发,而不是"先加锁"
设计并发代码的核心问题应是:
我是否明确建立了线程间的因果关系?
5.2 JMM 不能替你解决的问题
- 复合操作的一致性
- 业务不变式
- 高层并发策略
JMM 只是:
并发世界的物理定律,而不是业务逻辑的守护神
六、演进与对比视角(长期价值)
- JSR-133:确立现代 JMM
- Java 内存模型:相对"强"的语言级模型
- 对比 C/C++:Java 用性能换可推理性
关联内容(自动生成)
- [/编程语言/JAVA/JAVA并发编程/基础概念.html](/编程语言/JAVA/JAVA并发编程/基础概念.html) 涵盖了Java并发编程的基本概念,包括原子性、可见性、有序性等与JMM密切相关的特性
- [/编程语言/JAVA/JAVA并发编程/JAVA并发编程.html](/编程语言/JAVA/JAVA并发编程/JAVA并发编程.html) 介绍了Java并发编程的整体概念,与JMM作为Java并发语义基础密切相关
- [/编程语言/JAVA/JAVA并发编程/线程池.html](/编程语言/JAVA/JAVA并发编程/线程池.html) 线程池是Java并发编程的重要组成部分,其设计和实现与JMM的内存可见性和线程同步机制密切相关
- [/编程语言/JAVA/JAVA并发编程/并发集合.html](/编程语言/JAVA/JAVA并发编程/并发集合.html) 并发集合的实现依赖于JMM提供的内存模型和同步机制,是JMM在实际开发中的重要应用
- [/编程语言/JAVA/JAVA并发编程/并发工具类.html](/编程语言/JAVA/JAVA并发编程/并发工具类.html) Java并发工具类的实现基于JMM提供的同步原语,是JMM在实际开发中的具体体现
- [/编程语言/并发模型.html](/编程语言/并发模型.html) 探讨了不同编程语言中的并发模型,有助于理解JMM在并发编程语言设计中的地位和作用
- [/操作系统/内存管理.html](/操作系统/内存管理.html) 操作系统内存管理机制是JMM实现的基础,理解底层内存管理有助于深入理解JMM的设计原理