JMM 与 volatile
2025/12/19大约 2 分钟约 693 字
JMM(Java 内存模型)
什么是 JMM
JMM 是 Java 虚拟机规范中定义的一套规则,用来规范多线程访问共享变量的行为,保证程序在各种平台上都能正确地并发执行。
主内存与工作内存
┌─────────────────────────────────────────────────────┐
│ 主内存 (共享) │
│ 共享变量:x = 0, flag = false │
└───────────┬────────────────────────┬────────────────┘
│ │
read/write read/write
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ 工作内存 (线程1) │ │ 工作内存 (线程2) │
│ 私有,存变量副本 │ │ 私有,存变量副本 │
└──────────────────┘ └──────────────────┘- 主内存:所有线程共享,存储共享变量
- 工作内存:线程私有,存储变量副本
- 操作流程:从主内存读取 → 工作内存操作 → 写回主内存
易错点:主内存不是"最新的",是"共享的原始来源"。可见性问题就是因为工作内存副本可能比主内存旧。
三大问题
| 问题 | 说明 | 解决方案 |
|---|---|---|
| 可见性 | 线程修改后,其他线程看不到 | volatile、synchronized、Lock |
| 有序性 | 指令重排序导致执行顺序变化 | volatile、synchronized |
| 原子性 | 复合操作被打断 | synchronized、Lock、Atomic 原子类 |
易错点:原子性的解决方案不只有锁,还有 Atomic 原子类(基于 CAS)。
volatile 关键字
两个核心作用
| 作用 | 说明 |
|---|---|
| 保证可见性 | 写立即刷主内存,读从主内存取 |
| 禁止指令重排序 | 防止编译器和 CPU 重排指令 |
注意:volatile 不保证原子性!
可见性示例
volatile boolean flag = false;
// 线程1
flag = true; // 立即刷新到主内存
// 线程2
while (!flag) { // 每次从主内存读取
// 能正确退出循环
}禁止重排序:双重检查锁定单例
public class Singleton {
private static volatile Singleton instance; // 必须加 volatile
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // new 不是原子操作
}
}
}
return instance;
}
}new 对象分三步:
- 分配内存
- 初始化对象
- 指向内存地址
如果重排序成 1→3→2:
- 其他线程可能拿到未初始化的"半成品"对象
- volatile 禁止重排序,保证 1→2→3 顺序
不保证原子性
volatile int count = 0;
count++; // 不是原子操作!
// 分为:读取 → +1 → 写回
// 多线程下会丢失更新解决:使用 synchronized 或 AtomicInteger
volatile vs synchronized
| 对比 | volatile | synchronized |
|---|---|---|
| 可见性 | ✓ | ✓ |
| 有序性 | ✓ | ✓ |
| 原子性 | ✗ | ✓ |
| 阻塞 | 不阻塞 | 阻塞 |
| 适用 | 状态标志 | 复合操作 |
适用场景
- 状态标志:
volatile boolean running - 双重检查锁定单例
- 一写多读场景
总结
| 概念 | 说明 |
|---|---|
| JMM | 规范多线程访问共享变量的行为 |
| 主内存 | 共享变量存储区 |
| 工作内存 | 线程私有,存变量副本 |
| volatile | 保证可见性、禁止重排序,不保证原子性 |
