JMM Java 内存模型(Java Memory Model)
目录
一、JMM 是什么
核心分层模型(主内存 + 工作内存)
二、并发三大特性 & JMM 对应的问题
1. 可见性 Visibility
2. 原子性 Atomicity
3. 有序性 Ordering
三、JMM 核心:happens-before 先行发生原则
四、内存屏障(volatile 底层实现)
五、synchronized 在 JMM 的语义
六、volatile 关键特性总结
DCL 单例为什么必须加 volatile
七、JMM 与 CPU 缓存一致性
八、易混淆区分
九、高频面试精简考点
一、JMM 是什么
JMM 是一套抽象规范,定义了多线程环境下:
- 线程如何访问共享变量
- 变量在主内存、工作内存之间的读写交互规则
- 可见性、原子性、有序性三大并发问题解决方案注意:JMM ≠ JVM 运行时数据区(堆 / 栈 / 方法区),它是逻辑模型,不是物理内存划分。
核心分层模型(主内存 + 工作内存)
- 主内存(Main Memory)共享变量存放处,所有线程共享,对应堆中实例字段、静态变量。
- 工作内存(Working Memory)每个线程私有,线程操作变量只能拷贝副本到工作内存读写,不能直接操作主内存。 对应虚拟机栈局部变量、寄存器、CPU 缓存。
交互流程:
主内存 <--(读写同步)--> 线程工作内存(副本)线程修改变量步骤:
- 从主内存拷贝变量副本到工作内存
- 在工作内存读写副本
- 刷新副本写回主内存(时机不固定,无同步会延迟)
二、并发三大特性 & JMM 对应的问题
1. 可见性 Visibility
问题:A 线程修改副本后没立刻刷回主内存,B 线程看不到最新值。 解决方案:volatile、synchronized、Lock
2. 原子性 Atomicity
问题:多步操作被打断(如i++= 读 + 加 + 写,三步非原子),多线程交错导致数据错乱。 解决方案:synchronized、Lock、Atomic原子类
3. 有序性 Ordering
问题:CPU / 编译器指令重排,代码书写顺序和实际执行顺序不一致,多线程出现诡异 bug。 重排三种:编译器重排、CPU 指令重排、内存屏障重排。 解决方案:volatile(禁止前后重排)、synchronized、happens-before
三、JMM 核心:happens-before 先行发生原则
不用锁 /volatile 也能天然保证有序、可见,8 条规则:
- 程序顺序规则:单线程内,前面代码 happens-before 后面代码
- 监视器锁规则:unlock 锁 happens-before 后续同一个 lock 锁
- volatile 变量规则:对 volatile 写 happens-before 后续对该变量读
- 线程启动规则:
start()happens-before 线程内所有操作 - 线程终止规则:线程内所有操作 happens-before
join()返回 - 中断规则:
interrupt()调用 happens-before 检测中断代码 - 对象终结规则:对象初始化完成 happens-before
finalize() - 传递性:A hb B,B hb C → A hb C
只要满足 happens-before,前操作对后操作可见、有序。
四、内存屏障(volatile 底层实现)
volatile 通过插入内存屏障禁止重排,四种屏障:
- LoadLoad:读 1,LoadLoad,读 2 → 读 1 先执行完再读 2
- StoreStore:写 1,StoreStore,写 2 → 写 1 刷新主存再写 2
- LoadStore:读 1,LoadStore,写 2 → 读完再写
- StoreLoad:写 1,StoreLoad,读 2(开销最大,volatile 写后必加)
volatile 读写屏障规则:
- 写 volatile:前面所有读写不能重排到写之后;写完插入 StoreLoad 屏障
- 读 volatile:后面所有读写不能重排到读之前
五、synchronized 在 JMM 的语义
- 原子性:临界区代码同一时间仅一个线程执行
- 可见性:
- 加锁 lock:清空工作内存副本,从主内存重新加载变量
- 解锁 unlock:把工作内存修改全部刷新回主内存
- 有序性:临界区内代码不会和外部指令跨锁重排,满足监视器 hb 规则
六、volatile 关键特性总结
- 保证可见性:写后立刻刷主存,读时强制从主存加载
- 保证有序性:禁止指令重排
- 不保证原子性:复合操作(i++)依旧线程不安全 适用场景:状态标记、双重检查锁 DCL 单例
DCL 单例为什么必须加 volatile
不加 volatile 时,对象创建三步会被重排:
- 分配内存
- 引用赋值给 instance
- 初始化对象 重排后顺序 1→2→3,其他线程拿到半初始化的 instance,出现空指针 / 脏数据。 volatile 禁止 2、3 重排,解决问题。
七、JMM 与 CPU 缓存一致性
现代 CPU 多级缓存 (L1/L2/L3),硬件层面有缓存一致性协议 (MESI),但硬件开销大。 JMM 在语言层面屏蔽不同 CPU 硬件差异,统一一套并发内存语义,让 Java 跨平台并发行为一致。
八、易混淆区分
- JMM:语言内存模型,并发规范,解决多线程可见 / 原子 / 有序
- JVM 运行时数据区:堆、虚拟机栈、本地方法栈、程序计数器、元空间,是内存物理划分
- 硬件内存模型:CPU 缓存、MESI 缓存协议,底层硬件实现
九、高频面试精简考点
- i++ 线程不安全原因:无原子性、无可见性、指令重排
- volatile 和 synchronized 区别:volatile 轻量,无原子;synchronized 独占锁,三者都保证
- happens-before 是什么:JMM 的有序可见判断依据
- 内存屏障作用:阻止指令重排,强制刷新缓存
- DCL 单例 volatile 作用:防止对象初始化指令重排
