Java 内存模型(JMM)详解
Java 内存模型(JMM)详解
1. 为什么需要 JMM?
在 CPU 多核架构下,每个核心有自己的缓存(L1/L2 Cache),多线程程序中不同线程对共享变量的修改可能无法即时可见,导致可见性问题。Java 内存模型(JMM)就是为了定义多线程环境下变量访问规则而设计的。
JMM 解决三大问题:可见性、原子性、有序性。
2. JMM 核心概念
2.1 主内存与工作内存
┌──────────────────────────────────────┐
│ 主内存 (Main Memory) │
│ 共享变量 x = 0 │
└──────┬───────────────────┬───────────┘
│ read/write │ read/write
┌──────▼──────┐ ┌──────▼──────┐
│ 线程 A │ │ 线程 B │
│ 工作内存x=1 │ │ 工作内存x=0 │
└─────────────┘ └─────────────┘
- 主内存:所有线程共享,存放实例字段、静态字段等
- 工作内存:每个线程私有,存放该线程使用的变量副本
2.2 happens-before 原则
JMM 通过 happens-before 规则来保证内存可见性,主要规则:
| 规则 | 说明 |
|---|---|
| 程序顺序规则 | 同一线程内,代码按书写顺序执行 |
| volatile 规则 | volatile 写 happens-before 后续的 volatile 读 |
| 锁规则 | unlock happens-before 后续对同一锁的 lock |
| 线程启动规则 | Thread.start() happens-before 线程内的操作 |
| 传递性 | A hb B,B hb C,则 A hb C |
3. volatile 关键字
volatile 保证可见性和禁止指令重排,但不保证原子性。
public class VolatileExample {
// volatile 保证主内存更新对所有线程立即可见
private volatile boolean running = true;
public void stop() {
running = false; // 立即刷到主内存
}
public void run() {
while (running) { // 每次从主内存读取最新值
doWork();
}
}
}
volatile 底层原理
volatile 写操作会在指令前后加入内存屏障(Memory Barrier):
StoreStore 屏障
volatile 写
StoreLoad 屏障 ← 最重要,防止 volatile 写与后续读重排
4. synchronized 与 JMM
synchronized 同时保证可见性、原子性、有序性。
public class SyncExample {
private int count = 0;
// synchronized 保证同一时刻只有一个线程执行
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count; // 解锁前刷新到主内存,加锁后从主内存读取
}
}
5. 常见面试题
Q:double/long 类型操作是原子的吗?
A:JVM 规范允许将 64 位的 long/double 的读写操作分成两次 32 位操作,在 32 位 JVM 上不是原子的。实际上现代 64 位 JVM 已经保证了原子性,但建议加 volatile 修饰。
Q:volatile 能替代 synchronized 吗?
A:不能。volatile 不保证复合操作的原子性(如 i++),只能在"一写多读"场景下替代 synchronized。
💡 总结:JMM 通过 happens-before、volatile、synchronized 三套机制保证并发安全。理解 JMM 是掌握 Java 并发编程的基础。


💬 文章评论 (0)