volatile 是如何保证可见性和有序性的?¶
一、可见性¶
volatile 修饰的变量,一个线程修改后,其他线程能立即看到新值。
原理¶
- 写:改完立即刷新到主内存(或让其他核缓存行失效)。
- 读:每次都从主内存(或最新缓存)读,不用本地缓存。
底层通过 MESI 协议:写 volatile 变量时,其他核的缓存行被标记为 I(Invalid),下次读必须重新从内存加载。
二、有序性¶
volatile 禁止指令重排:
- 写 volatile 之前的操作不能排到 volatile 写之后。
- 读 volatile 之后的操作不能排到 volatile 读之前。
典型应用:双重检查锁单例¶
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // volatile 防止重排
}
}
}
return instance;
}
new Singleton() 分三步:分配内存 → 初始化 → 引用赋值。没有 volatile 可能重排成 分配 → 引用赋值 → 初始化,其他线程拿到非 null 但未初始化的对象。
三、volatile 不保证原子性¶
i++ 是读-改-写三步,volatile 只保证每次读到最新值,不保证三步不被打断。要原子性用 AtomicInteger 或 synchronized。
四、对比¶
| volatile | synchronized | |
|---|---|---|
| 可见性 | ✅ | ✅ |
| 有序性 | ✅ | ✅ |
| 原子性 | ❌ | ✅ |
| 性能 | 轻量(无锁) | 重量(阻塞) |
五、适用场景¶
- 状态标志位:
volatile boolean running = true; - 双重检查锁单例。
- 一写多读。
常见误区
- volatile 不能替代锁。
- volatile 不能保证
i++原子性。