06 面试官:volatile 到底解决了什么问题?大部分人答了三个字就跪了

有一回面 Java 岗,我随口问了一句:"volatile 关键字有什么用?"
面试者眼睛都没眨:"保证可见性,禁止指令重排。"
说得好——基本上是标准答案。但我觉得还不够。我接着问:
"那我写一个 volatile int count = 0,然后 10 个线程同时 count++,最后 count 是不是等于 10?"
他犹豫了半天:"……应该是,因为 volatile 保证可见性。"
我当场给了他一个无害的笑。
因为 10 个线程同时 count++,冲这个 volatile 可见性没半毛钱用。volatile 不保证原子性。 下一秒他被问住了。
这个坑 80% 的人都掉过。因为网上的资料把 volatile 讲得玄乎其神,"可见性""有序性"这俩词一排列组合——谁也闹不清真正运作时是怎么回事。
volatile 真正干了两件事——不多,就是两件
第一件:确保变量的值直接从主内存里读、写到主内存里去
这句话简单——但后面藏着计算机系统最底层的两个坑:CPU 缓存和编译器优化。
你写了一个 boolean flag = true。你觉得它就该立马生效——但在计算机眼里,它可能被塞到 CPU 的 L1 或者 L2 缓存里了。别的 CPU 核心根本看不见你改的东西。
volatile 就是一件硬东西。它告诉编译器和 CPU:对这个变量的读写,每一步都直接走主内存——绕过 CPU 缓存。
这就保证了一个线程修改 volatile 变量后,其他线程立即看到最新值。
但只是值本身。 它不保护你拿这个值去做多步操作。比如 volatile int count; count++——这其实是三步操作:读值、加 1、写值。volatile 保证每一步读和写都能看到最新值——但它不保证这三步是一个原子操作。
第二件:禁止指令重排——解决一个更深的坑
这一件比第一件更难看到——但它是 volatile 最常用的场景。
指令重排是个啥?
编译器为了性能,可能会重新排列你的代码顺序。比如你写了:
// 线程 A
config = newConfig(); // ①
initialized = true; // ②
// 线程 B
while (!initialized) {} // ③
config.doSomething(); // ④
你以为 ① 一定在 ② 前面执行——但 CPU 觉得"我先执行 ② 也没啥问题(② 不吃 ① 的结果)"——于是把 ② 提到 ① 前面了。
结果就是:线程 B 看到 initialized = true 了,就去调 config.doSomething(),但 config 还没初始化完——空指针。这也叫"对象逸出"问题。
// volatile 就解决这个:
private volatile boolean initialized = false;
volatile 告诉编译器和 CPU:这个变量前面的代码不准跑到它后面,后面的代码不准跑到它前面。 这就强制 ① 先于 ②——杜绝了对象逸出。
volatile 经典用例:单例模式的双重检查锁
这是面试中 volatile 最常见的使用场景——不知道的话,DCL(双重检查锁)会被面试官怼个底朝天:
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(); // ← 问题出在这一行
}
}
}
return instance;
}
}
为什么这里必须用 volatile?

因为 instance = new Singleton() 这一行,JVM 拆成了三步:
① 分配内存空间
② 初始化对象(调用构造方法)
③ 将 instance 引用指向分配好的内存空间
如果没有 volatile,JVM 可能把②和③重排——先把内存空间地址赋给 instance(③),再初始化对象(②)。
这时候,线程 A 在同步块里刚刚执行完③——instance 已经不为 null 了但还没完成初始化。线程 B 过来做第一次检查——发现 instance 不为 null!直接就拿去用了一个半初始化的对象。爆炸。
volatile 禁止重排,保证了②一定在③之前——DCL 才正确。
再深挖一层:volatile 的底层实现
面试官下一步可能会逼你一句:"volatile 在底层是怎么实现的?"
Java 内存模型(JMM)规定,volatile 变量的写操作前后会插入内存屏障。
具体来说,通过 lock 前缀指令实现:
- 写 volatile 时:JVM 在写操作后插入一个 store-store 屏障 + store-load 屏障。确保当前 CPU 的写缓冲刷新到主内存,并让其他 CPU 的缓存失效。
- 读 volatile 时:JVM 在读操作前后插入 load-load 屏障 和 load-store 屏障。确保读操作从主内存拉最新值,并禁止后面的读写被重排到前面。
说得更直白一点——volatile 的代价是:每次读写都穿透 CPU 缓存,直击主内存。 所以 volatile 比普通变量慢——但它保证了你程序的正确性。
如果你能解释到这一步——面试官会问你:"那你有考虑过 volatile 的性能吗?"这问题直接证明了他已经认可你对 volatile 的理解。
面试分三档
第一档:答得出"保证可见性、禁止指令重排"——及格线附近。说明看过面经。
第二档:在第一档基础上,能解释 i++ 不是原子的 + 能用内存屏障说明底层实现 + DCL 中的实际应用——通过线。说明真正用过 volatile、踩过坑。
第三档:在第二档基础上,能主动说:"volatile 保证可见性不保证原子性——这俩是两码事,一个控制缓存一致性、一个控制操作原子性。你把 volatile 用在单线程对多线程的通话场景(比如开关标识),或者与锁配合用——不要想用 volatile 替代锁。"
关键就在最后一句话。 面试官从这句里看到:你知道 volatile 能干什么、不能干什么、边界在哪——你不是背面试题的。你是一个真正用了它的人。
volatile 你踩过什么坑?有没有线上因为 volatile 造成的 bug 让你抓狂过?留言区唠唠。
本文作者:资深 Java 开发老兵,前大厂面试官。不讲八股文,只讲面试官心里在想什么。系列持续更新,关注不迷路。
如果觉得有帮助,点个关注支持一下。
我最近整理了一份《大厂面试老兵资料包》,里面有评分表、高频场景题还有今年新出的 AIGC 面试题,
都是 200 多场面试里一点点攒的,市面上没人这么整理过。
需要的朋友私信我回复「666」,我发你领取方式。
更多推荐



所有评论(0)