volatile 到底做了什么?
太棒了!这个结构和深度已经非常好了。为了让它达到"教科书级"的清晰度,我主要在逻辑流畅性和视觉引导上做一次精炼优化。
一、问题根源:为什么需要 volatile?
案例1:永不停止的线程
arduino
public class NeverStop {
static boolean running = true; // 危险:非 volatile
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
while (running) { /* 空转 */ } // 可能永远循环
});
worker.start();
Thread.sleep(1000);
running = false; // 主线程修改,工作线程可能看不见
}
}
📉 问题本质:缓存不一致

案例2:DCL单例的"半成品"对象
csharp
instance = new Singleton(); // 实际分三步执行:
// 1. 分配内存空间
// 2. 初始化对象
// 3. 赋值引用
// 可能被重排序为:1 → 3 → 2
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // 可能重排序!
}
}
}
return instance;
}
📉 问题本质:指令重排序
ini
线程A创建实例:
1. 分配内存
2. [重排序] instance = 地址 ← 引用提前发布!
3. 初始化对象
此时线程B检查:
if (instance != null) → true
return instance; → 拿到未初始化的对象!
二、volatile 的解决方案
解决方案1:强制可见性
arduino
static volatile boolean running = true;
📈 执行流程对比图:

🔧 底层机制:
- 写操作:强制刷新到主内存,并使其他CPU中该变量的缓存行失效
- 读操作:强制从主内存重新加载最新值
- 通信协议:基于CPU的MESI缓存一致性协议
解决方案2:禁止重排序
arduino
private static volatile Singleton instance;
// new Singleton() 的执行顺序被固定:
// 1. 分配内存 → 2. 初始化 → 3. 赋值引用
🔧 底层机制:内存屏障
JVM在volatile操作前后插入内存屏障:
屏障类型 | 插入位置 | 作用 |
---|---|---|
StoreStore | volatile写之前 | 保证前面所有写操作先完成 |
StoreLoad | volatile写之后 | 保证写操作对其他线程立即可见 |
LoadLoad | volatile读之后 | 保证后面读操作不会重排到前面 |
LoadStore | volatile读之后 | 保证后面写操作不会重排到前面 |
特性 | 普通变量 | volatile变量 |
---|---|---|
可见性 | 无保证 | ✅ 强保证 |
原子性 | 无保证 | ❌ 仍无保证(除long/double) |
有序性 | 无保证 | ✅ 禁止重排序 |
性能 | 最优 | 轻量级同步代价 |
💡 核心价值:volatile在可见性和有序性上提供轻量级保证,是介于"无同步"与"重量级锁"之间的优雅折中。