Java 内存模型(JMM)与 happens-before 原则:面试官最爱的底层题,一文搞懂
文章目录
《Java 内存模型(JMM)与 happens-before 原则:面试官最爱的底层题,一文搞懂》
一、前言
读者你好,我是程序员卷卷狗。
在最近的 Java 面试中,我发现“Java 内存模型(JMM)”和“happens-before 原则”几乎是面试官的固定提问项。
今天这一篇,我会先用一句话版答案帮你背下来,再用多角度分析帮你彻底理解。
二、一句话速记版(面试速背)
Java 内存模型(JMM) 是一种规范,用来定义多线程访问共享变量时的可见性、原子性、有序性问题。
它通过“主内存 + 工作内存”机制 + “happens-before 规则”保证线程之间的数据一致性。
核心要背的:
1. 原子性:单步操作不可分割(如 i++ 不是原子)
2. 可见性:一个线程修改后,其他线程能及时看到(volatile 保证)
3. 有序性:编译器和 CPU 不会随意重排(happens-before 规则保证)
三、深入理解 JMM 原理
(1)JMM 是什么?
JMM 并不是实际的内存结构,而是一种 抽象的规范。
Java 线程之间的共享变量存在主内存中,每个线程有自己的 工作内存(CPU 缓存)。
线程只能操作自己工作内存的副本,修改后再同步回主内存。
比喻理解:
主内存像一个“共享仓库”,线程的工作内存像“快递点”,拿货→修改→再放回。
(2)为什么需要 JMM?
因为 CPU 优化和指令重排会导致线程看到的数据不一致。
例如:
int a = 0;
boolean flag = false;
new Thread(() -> {
a = 1;
flag = true;
}).start();
new Thread(() -> {
if (flag) {
System.out.println(a);
}
}).start();
在单线程下输出必然是
1,但多线程下可能输出0—— 因为flag=true 的写入对另一个线程不可见。
四、happens-before 原则(记口诀)
口诀:程序顺序锁传递
| 规则 | 含义 | 举例 |
|---|---|---|
| 1. 程序次序规则 | 同一线程内,按代码顺序执行 | A→B→C |
| 2. 锁规则 | unlock 必定发生在后续 lock 之前 | synchronized |
| 3.volatile 规则 | 对 volatile 变量的写先于读 | 写→读 |
| 4. 传递性 | A→B,B→C ⇒ A→C | happens-before 可传递 |
| 5. 线程启动规则 | start() 之前的操作先于线程 run() | |
| 6. 线程终止规则 | run() 中的操作先于 join() 返回 | |
| 7. 对象初始化 | 构造函数结束,才对其他线程可见 |
五、volatile 与 JMM 的关系
volatile 关键字的作用:
- 保证 可见性(写入立即刷新主内存)
- 禁止 指令重排(保证执行顺序)
但不保证 原子性(i++ 依旧不安全)
对比:
| 特性 | synchronized | volatile |
|---|---|---|
| 可见性 | ✅ | ✅ |
| 原子性 | ✅ | ❌ |
| 有序性 | ✅ | ✅(通过内存屏障) |
六、面试延伸:JMM 与双重检查锁单例(DCL)
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton();
}
}
}
return instance;
}
}
为什么要加 volatile?
因为对象创建不是原子操作:分配内存 → 初始化 → 引用赋值。
没有 volatile,指令重排可能导致返回一个“未初始化完成”的对象。
七、总结口诀(方便记忆)
主工分离要牢记,可见原子有序齐;
顺序锁传volatile,DCL加它防乱序。
八、面试官常追问(附回答示例)
| 问题 | 参考回答 |
|---|---|
| ① happens-before 是硬件指令还是规范? | 是 JVM 层的抽象规范,不直接对应机器指令。 |
| ② volatile 能代替 synchronized 吗? | 不行,volatile 不保证原子性。 |
| ③ 指令重排是怎么禁止的? | 通过内存屏障(Memory Barrier)实现。 |
| ④ 为什么要引入工作内存? | 提升 CPU 缓存效率,避免频繁主内存访问。 |
九、结语
如果你能把这篇文章里的口诀和表格吃透,
面试中任何“JMM / volatile / happens-before”问题都能稳拿。
下一篇,我会写:《volatile 与 synchronized 深度对比与应用场景》。
更多推荐


所有评论(0)