volatile 是 Java 中的一个关键字,用于修饰变量,主要解决多线程环境下的内存可见性问题和指令重排序问题。
主要作用
- 保证可见性:当一个线程修改了 volatile 变量的值,新值会立即被刷新到主内存中,其他线程读取时会直接从主内存读取最新值。
- 禁止指令重排序:防止 JVM 对 volatile 变量相关的代码进行指令重排序优化。
基本用法
public class SharedObject {
public volatile int counter = 0;
}
volatile 的特性
1. 内存可见性保证
没有 volatile 修饰时可能出现的问题:
// 没有 volatile 修饰
boolean running = true;
void work() {
while(running) {
// 工作代码
}
}
void stop() {
running = false;
}
在这个例子中, stop() 方法修改 running 的值后, work() 方法的循环可能不会立即停止,因为工作线程可能在自己的工作内存中保留了 running 的旧值。
使用 volatile 可以解决:
volatile boolean running = true;
2. 禁止指令重排序
volatile 通过插入内存屏障来防止指令重排序,这在单例模式的双重检查锁定中很重要:
class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
如果不使用 volatile, instance = new Singleton() 可能会被重排序,导致其他线程看到未完全初始化的对象。
volatile 的局限性
1. 不保证原子性:volatile 不能替代 synchronized,它不保证复合操作的原子性。
错误示例:
volatile int count = 0;
count++; // 这不是原子操作
count++ 实际上是读-改-写三个操作,volatile 不能保证这三个操作的整体原子性。
2. 不适用于依赖当前值的操作:如 count = count + 1 或 count++
volatile 适用场景
- 状态标志位(如前面示例中的 running )
- 单次安全发布(如单例模式的双重检查锁定)
- 独立观察(定期发布观察结果供程序使用)
- "开销较低的读-写锁策略"(读远多于写的情况)
volatile 与 synchronized 比较
特性 | volatile | synchronized |
作用范围 | 变量 | 变量、代码块、方法 |
内存可见性 | 保证 | 保证 |
原子性 | 不保证 | 保证 |
阻塞 | 不会导致线程阻塞 | 可能导致线程阻塞 |
性能 | 更高 | 相对较低 |
底层原理
volatile 的实现依赖于 JVM 的内存屏障:
- 写操作前插入 StoreStore 屏障
- 写操作后插入 StoreLoad 屏障
- 读操作前插入 LoadLoad 屏障
- 读操作后插入 LoadStore 屏障
这些屏障保证了 volatile 变量的读写操作不会被重排序,并且写操作后会立即刷新到主内存。