外观
一句话答案
volatile 保证可见性(写入立即刷新主内存)和有序性(禁止指令重排),但不保证原子性,底层靠内存屏障实现。
核心要点
volatile 的两大作用:
1. 可见性(Visibility)
- 对
volatile变量的写操作立刻刷新到主内存 - 对
volatile变量的读操作每次从主内存读取最新值(不使用 CPU 缓存/工作内存) - 底层:通过
lock前缀指令,使写操作同时令其他 CPU 的缓存行失效(缓存一致性协议 MESI)
2. 禁止指令重排序(有序性)
- 编译器和 CPU 出于性能优化会对指令重排序,
volatile通过内存屏障(Memory Barrier)禁止特定方向的重排序 - 规则:volatile 写操作之前的代码不能被重排到写之后;volatile 读之后的代码不能被重排到读之前
经典应用——双重检查单例(DCL):
java
class Singleton {
// 必须加 volatile!防止"半初始化对象"被另一个线程看到
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) { // 第一次检查(无锁)
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查(持锁)
instance = new Singleton();
// 如果没有 volatile,new Singleton() 分三步:
// 1. 分配内存
// 2. 初始化对象
// 3. 将 instance 指向内存
// 2 和 3 可能被重排:另一线程看到 instance != null 但对象未初始化
}
}
}
return instance;
}
}volatile vs synchronized:
| 维度 | volatile | synchronized |
|---|---|---|
| 可见性 | ✅ 保证 | ✅ 保证 |
| 有序性 | ✅ 禁止重排 | ✅ 进出同步块有内存屏障 |
| 原子性 | ❌ 不保证(i++ 不安全) | ✅ 保证同步块内原子执行 |
| 互斥 | ❌ 不互斥(多线程可同时读写) | ✅ 同一时刻只有一个线程执行 |
| 性能 | 轻量(无锁,只有内存屏障) | 重量(偏向→自旋→阻塞,有上下文切换风险) |
| 适用场景 | 状态标志位、单次赋值 | 复合操作(先读后写)、互斥区 |
追问与易错
追问方向:
- volatile 能保证原子性吗?举个例子?(不能,i++ 是读-改-写三步)
- volatile 禁止重排的底层原理?(编译器屏障 + CPU 内存屏障)
- DCL 单例为什么需要 volatile?(防止对象未完全初始化就被其他线程使用)
易错点:
- ❌ "volatile 可以替代 synchronized"——volatile 不保证原子性
- ❌ "volatile 变量读写有锁"——无锁,靠 CPU 缓存一致性协议(MESI)
💡 记忆锚点
volatile 是内存世界的"实时广播":写了立刻广播给所有核心(可见性),并且禁止广播前后的消息乱序(有序性)。但它管不了"读-改-写"这种三步操作的原子性——i++ 不是一句话而是三句话,中间随时可能被插嘴。DCL 单例不加 volatile,别人可能拿到一个"壳子造好了但内部还没装修"的半成品对象。