volatile关键字
一、是什么?
volatile
是Java中的关键字,用于修饰变量。它通过以下机制确保多线程环境下的数据安全:
- 可见性:当一个线程修改
volatile
变量时,其他线程能立即看到最新值。 - 禁止指令重排序:防止编译器和CPU优化时打乱指令顺序(通过内存屏障实现)。
二、解决什么问题
内存可见性问题
默认情况下,线程操作变量时可能从本地缓存(如CPU寄存器)读取旧值,导致其他线程的修改不可见。
示例问题:线程A修改共享变量flag
,线程B因缓存未更新而读取旧值。volatile
强制所有读写直接操作主内存,保证可见性。指令重排序问题
编译器/CPU可能调整无关指令的执行顺序以提高性能,但在多线程下可能导致逻辑错误。
示例问题:单例模式中,未初始化完的对象被其他线程使用(双重检查锁定失效)。
三、应用场景
状态标志位
简单布尔标志(如停止线程的isRunning
),无需原子性操作。javavolatile boolean isRunning = true; void stop() { isRunning = false; } // 线程A调用 void task() { while (isRunning) { ... } } // 线程B读取
单例模式(双重检查锁定)
确保对象初始化完成后再被访问(JDK 5+生效)。javaclass Singleton { private static volatile Singleton instance; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); // 禁止重排序 } } } return instance; } }
独立观察的变量
变量被多个线程读取,但仅由单个线程写入(如配置更新)。
四、Java示例
可见性演示
java
public class VolatileDemo {
volatile int counter = 0; // 移除volatile会导致死循环
public static void main(String[] args) throws InterruptedException {
VolatileDemo demo = new VolatileDemo();
new Thread(() -> {
while (demo.counter == 0) {} // 循环检测
System.out.println("Counter changed!");
}).start();
Thread.sleep(1000);
demo.counter = 1; // 主线程修改
}
}
五、与synchronized的区别
特性 | volatile | synchronized |
---|---|---|
作用范围 | 仅修饰变量 | 修饰方法/代码块 |
原子性 | ❌ 不保证(如i++ ) | ✅ 保证 |
可见性 | ✅ 保证 | ✅ 保证 |
有序性 | ✅ 禁止重排序 | ✅ 保证 |
线程阻塞 | ❌ 不阻塞 | ✅ 阻塞(重量级锁) |
性能开销 | 低 | 较高 |
六、重要注意事项
不保证原子性
volatile
无法解决复合操作(如count++
)的线程安全问题。需改用AtomicInteger
或synchronized
。
错误示例:javavolatile int count = 0; count++; // 非原子操作(读取+修改+写入)
适用场景有限
仅适用于满足以下条件的场景:- 变量独立于其他状态(不依赖旧值)。
- 写操作不依赖当前值(如直接赋值
flag=true
)。
JDK 5+的内存模型改进
volatile
的语义在JDK 5中通过JSR-133被强化(增强有序性保证),旧版本行为可能不一致。替代方案
- 需要原子性 → 使用
java.util.concurrent.atomic
包(如AtomicInteger
)。 - 复杂同步 → 使用
ReentrantLock
或synchronized
。
- 需要原子性 → 使用
七、总结
volatile
是轻量级的线程同步工具,核心解决可见性和有序性问题,但不保证原子性。适用于状态标志、单例模式等简单场景。在JDK 5+中,其语义通过内存屏障严格实现,但需谨慎评估是否满足原子性需求。对于复杂操作,优先考虑原子类或显式锁机制。