说下你对volatile的理解
2026/6/26大约 3 分钟面试题面试题
一、 标准面试回答模版(建议背诵)
面试官: 说下你对 volatile 的理解。
Fox版标准回答:
“volatile 是 Java 虚拟机提供的轻量级同步机制。它的核心作用有两点,同时有一个致命的不足:
- 保证可见性 (Visibility):
- 当一个线程修改了
volatile修饰的变量,新值会被立即刷新到主内存。 - 同时,它会使其他线程的工作内存(缓存)中该变量的副本失效,强制它们重新从主内存读取。
- 底层依赖于 CPU 的 Lock 前缀指令 和 缓存一致性协议(如 MESI)。
- 保证有序性 (Ordering) / 禁止指令重排序:
- JMM 通过插入 内存屏障 (Memory Barrier) 来禁止特定类型的处理器重排序。
- 经典应用场景是 单例模式的双重检查锁 (DCL),防止对象‘初始化了一半’就被另一个线程拿去用了。
- 不保证原子性 (No Atomicity):
- 这是最大的坑。
volatile仅能保证单次读/写的原子性。 - 对于复合操作(如
i++),它无法保证线程安全。因为i++分为‘读-改-写’三步,volatile无法锁住这三步的间隙。”
二、 代码层面的体现
1. 场景一:保证可见性(最经典用法)
如果不加 volatile,线程 B 可能永远感知不到 flag 变了,导致死循环。
public class VolatileVisibilityDemo {
// 【关键】:加上 volatile,保证线程间可见
private static volatile boolean flag = false;
public static void main(String[] args) throws InterruptedException {
// 线程A:死等 flag 变为 true
new Thread(() -> {
System.out.println("等待线程开始...");
while (!flag) {
// 如果 flag 没有 volatile,这里可能读取的是 CPU 缓存里的旧值 (false)
// 导致死循环
}
System.out.println("检测到 flag 变为 true,任务结束!");
}).start();
Thread.sleep(1000);
// 线程B:修改 flag
new Thread(() -> {
System.out.println("修改线程把 flag 改为 true");
flag = true;
}).start();
}
}2. 场景二:不保证原子性(避坑演示)
证明 volatile 不能替代 synchronized。
public class VolatileAtomicityDemo {
// 即使加了 volatile
private static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
// 10个线程,每个加 1000 次
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
count++; // 这不是原子操作!
}
}).start();
}
// 等所有线程跑完(简单粗暴等待)
Thread.sleep(2000);
// 预期是 10000,实际结果通常小于 10000
System.out.println("最终结果: " + count);
}
}三、 Fox的深度解析(DCL 单例)
如果面试官问:“为什么单例模式的双重检查锁(DCL)一定要加 volatile?”
Fox版解析:
“这是为了防止指令重排序导致的‘半初始化对象’泄露。
instance = new Singleton(); 这行代码在 JVM 层面其实分三步:
memory = allocate();// 分配内存ctorInstance(memory);// 初始化对象(构造方法)instance = memory;// 把引用指向内存地址
如果不加 volatile,CPU 或编译器可能会把 步骤 2 和 步骤 3 重排序。 结果: 线程 A 先执行了步骤 3(引用有了地址),但还没执行步骤 2(对象还没初始化)。 此时线程 B 进来了,判断 instance != null,直接拿走了一个还没初始化的空壳对象去使用,导致程序报错。
volatile 通过内存屏障,强制保证了 1 -> 2 -> 3 的顺序。”
拓展视频: