一句话总结
JMM(Java Memory Model)是 Java 虚拟机规范中定义的并发编程内存模型,屏蔽了不同硬件和操作系统的内存访问差异。核心是主内存与工作内存的抽象:所有变量存在主内存,线程操作变量时先从主内存拷贝到工作内存。JMM 围绕原子性、可见性、有序性三大特性,通过 8 条 happens-before 规则来约束编译器和处理器的优化行为,保证多线程程序的正确性。
初级理解
JMM 是什么?
JMM 不是真实存在的物理内存,而是一个抽象概念。它定义了 Java 程序中变量(实例字段、静态字段、数组元素)的访问规则。
主内存 vs 工作内存
| 概念 | 对应物理 | 说明 |
|---|---|---|
| 主内存 | 物理内存(RAM) | 所有变量存储的地方,线程共享 |
| 工作内存 | CPU 缓存/寄存器 | 每个线程私有,存储变量的副本 |
线程不能直接操作主内存中的变量,必须先拷贝到工作内存,修改后再写回主内存。这就导致了可见性问题。
并发编程三大特性
| 特性 | 定义 | 实现方式 |
|---|---|---|
| 原子性 | 一个操作不可中断,要么全做要么全不做 | synchronized、Lock、CAS |
| 可见性 | 一个线程修改后,其他线程立即可见 | volatile、synchronized、final |
| 有序性 | 程序按代码顺序执行(禁止重排序) | volatile、synchronized |
中级深入
8 条 happens-before 规则
happens-before 是 JMM 的核心规则。如果操作 A happens-before 操作 B,那么 A 的结果对 B 可见,且 A 的执行顺序在 B 之前。
| # | 规则 | 说明 |
|---|---|---|
| 1 | 程序次序规则 | 同一线程内,前面的操作 happens-before 后面的操作 |
| 2 | 管程锁定规则 | unlock happens-before 后续的 lock(同一把锁) |
| 3 | volatile 规则 | volatile 写 happens-before 后续的 volatile 读 |
| 4 | 线程启动规则 | Thread.start() happens-before 线程中的任何操作 |
| 5 | 线程终止规则 | 线程中所有操作 happens-before Thread.join() 返回 |
| 6 | 线程中断规则 | interrupt() happens-before 被中断线程检测到中断 |
| 7 | 对象终结规则 | 构造函数结束 happens-before finalize() |
| 8 | 传递性 | A hb B, B hb C → A hb C |
volatile 的内存语义
synchronized 的内存语义
synchronized 的内存语义:进入同步块时清空工作内存中变量的值,从主内存重新读取;退出同步块时将工作内存中的修改刷新到主内存。
final 的内存语义
final 域的内存语义:在构造函数中对 final 域的写入,与随后通过该引用访问该 final 域之间,存在 happens-before 关系。这保证了 final 域在构造函数完成后对所有线程可见。
高级拓展
JMM 与硬件内存模型的关系
JMM 屏蔽了不同 CPU 架构的内存模型差异。x86 是强内存模型(TSO),只允许 Store-Load 重排序;ARM/PowerPC 是弱内存模型,允许更多重排序。JMM 通过内存屏障在不同平台上实现一致的语义。
双重检查锁定(DCL)为什么需要 volatile?
从 JMM 角度:没有 volatile 时,new 操作(分配内存、初始化、赋值引用)可能重排序,导致其他线程看到未初始化完成的对象。volatile 禁止了这种重排序。
JSR-133 对 JMM 的修复
JDK 5 之前的 JMM 存在严重缺陷(如 final 域可能被修改)。JSR-133 重新定义了 JMM,增强了 final 和 volatile 的语义。JDK 5+ 的 JMM 才是可靠的。
实战场景
场景一:利用 volatile 的 happens-before 传递性
场景二:安全发布对象
面试模拟
Q:JMM 是什么?
A:JMM 是 Java 虚拟机规范定义的并发内存模型,核心是主内存与工作内存的抽象。围绕原子性、可见性、有序性三大特性,通过 happens-before 规则约束编译器和 CPU 的优化,保证多线程程序的正确性。volatile、synchronized、final 都有各自的内存语义。
Q:happens-before 规则有哪些?
A:8 条规则:1) 程序次序(单线程内顺序);2) 管程锁定(unlock hb lock);3) volatile(写 hb 读);4) 线程启动(start hb 线程内操作);5) 线程终止(线程内操作 hb join);6) 线程中断(interrupt hb 检测中断);7) 对象终结(构造 hb finalize);8) 传递性(A hb B, B hb C → A hb C)。
Q:volatile 的 happens-before 规则有什么用?
A:volatile 写 happens-before volatile 读。结合传递性,volatile 写之前的所有操作的结果,对 volatile 读之后的所有操作都是可见的。这就是 DCL 单例中 volatile 能保证对象完全初始化的原因。