Java内存模型
约 1207 字大约 4 分钟
2025-08-26
MESI 协议
MESI 协议(Modified、Exclusive、Shared、Invalid)是一种缓存一致性协议,用于确保多处理器系统中各 CPU 缓存中共享变量副本的一致性。
- Modified (M):缓存行已被当前 CPU 修改,与主内存不一致
- Exclusive (E):缓存行仅被当前 CPU 持有,与主内存一致
- Shared (S):缓存行被多个 CPU 共享,与主内存一致
- Invalid (I):缓存行数据无效
其核心思想是:
- 当 CPU 执行写操作时,如果操作对象是共享变量(即其他 CPU 缓存中存在该变量的副本),会发出信号通知其他 CPU 将该变量的缓存行置为无效状态。
- 当其他 CPU 读取该变量时,发现其缓存行无效,则直接从主内存重新加载数据,从而保证数据一致性。
JMM 核心定义
目标与问题域
在 JMM 中,内存分为两类区域:
| 区域 | 特性 | 作用 |
|---|---|---|
| 主内存 | 线程共享 | 存储所有共享变量 |
| 工作内存 | 线程私有 | 存储主内存变量的副本 |
工作模式遵循以下流程:
- 线程操作共享变量时,先将变量从主内存拷贝到工作内存。
- 在线程工作内存中对副本执行操作。
- 操作完成后,将最新值刷新回主内存。
三大特性与解决方案
JMM 通过封装底层内存操作细节(如 MESI 协议、内存屏障),为开发者提供 synchronized、volatile 等抽象工具,解决多线程并发中的原子性、可见性、有序性问题。
原子性
问题描述
原子性问题指在多线程并发场景下,非原子操作可能因线程切换或竞争导致最终结果与预期不一致。典型案例如下:
i++非原子操作:该操作包含读取变量值、执行加法运算、写回新值三个子步骤。若多个线程同时执行,可能因线程交替执行导致最终结果错误。
解决方案
使用 synchronized 关键字实现同步控制:
synchronized通过内置锁(Monitor)机制,将代码块或方法封装为临界区(Critical Section),保证同一时刻仅有一个线程可以执行该区域内的代码。- 从而确保操作的原子性,避免多线程并发导致的数据不一致。
可见性
问题描述
可见性问题指某一线程对共享变量的修改,未能及时被其他线程感知,导致线程间数据状态不一致。其主要成因包括:
- 线程修改变量后,仅更新其工作内存(线程私有缓存),未立即刷回主内存。
- 其他线程仍从自身的工作内存中读取旧值,而非从主内存获取最新值。
解决方案
使用 volatile 关键字强制保证变量的可见性。
- 写入
volatile变量时,JVM 会向处理器发送lock前缀指令,强制将当前处理器缓存行的数据立即写回主内存,并触发缓存一致性协议(如 MESI)。 - 其他处理器通过嗅探机制(Snooping)监测到该写操作,会使本地缓存中对应缓存行失效,迫使后续读取必须从主内存重新加载最新数据。
有序性
问题描述
有序性问题指编译器或处理器为提高性能,可能对指令进行重排序(Reordering)。虽然在单线程环境下能保证最终结果一致,但在多线程并发中可能导致逻辑错误。典型案例如下:
- 双重检查锁定(DCL)单例模式:若实例变量未声明为
volatile,可能因指令重排序导致其他线程获取到未完全初始化的对象。
解决方案
使用 volatile 关键字禁止指令重排序。
public class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton(); // volatile 禁止重排序
}
}
}
return instance;
}
}volatile通过在指令序列中插入内存屏障(Memory Barrier)来限制重排序行为。内存屏障是一种 CPU 指令,用于阻止屏障前后的指令进行重排序。- JVM 会在编译期和运行期自动插入以下类型的内存屏障:
- 写操作前插入
StoreStore屏障 - 写操作后插入
StoreLoad屏障 - 读操作后插入
LoadLoad和LoadStore屏障 通过这些屏障,确保对volatile变量的读写操作按程序顺序执行,从而维护多线程环境下的有序性。
- 写操作前插入