Java 关键字 volatile
volatile 是 Java 提供的最轻量级的线程同步关键字,它的核心作用只有两件事,却正好是解决多核 CPU 高速缓存 + 指令重排序的两大痛点:
-
保证“可见性” 一个线程改了该变量,立刻对所有线程可见(跳过 CPU 缓存,直接刷回主内存)。
-
禁止“指令重排序”(JDK 5 后新增) 变量前后的读写操作不会被编译器/CPU 打乱顺序,从而避免多线程下的“半初始化”问题。
-
把变量声明为 volatile,读/写直接走主内存,立即可见。
-
同时插入内存屏障,阻止编译器重排序。
-
适用于一写多读或状态标志场景,复杂原子需求请用锁或原子类。
一句话记忆 “读直接从主内存拿,写完立即回主内存,并且不让编译器偷偷换顺序。”
典型使用场景
| 场景 | 示例代码 | 原因 |
|---|---|---|
| 状态开关 | volatile boolean running = true; | 一个线程改标志位,其他线程立刻看到停止指令。 |
| 单例双重检查 | private volatile static Singleton inst; | 防止 new 指令被重排序导致拿到“半初始化的对象”。 |
| 轻量计数器 | volatile long count; | 计数要求最终一致,而非严格原子递增(否则用 AtomicLong)。 |
代码演示:状态开关
class Task implements Runnable {
private volatile boolean running = true; // 1. 状态标志
public void run() {
while (running) { // 2. 工作循环
// do something
}
}
public void cancel() {
running = false; // 3. 另一线程调用,立即可见
}
}
不能做什么?
- 不是原子操作:volatile i++ 仍非线程安全(需要 AtomicInteger 或加锁)。
- 无法替代锁:不能保证多步操作的整体原子性。
在单线程程序中是否还有用?
单线程代码里写 volatile 是“无害但多余”的写法,既不提供正确性收益,也徒增轻微性能开销;只有在多线程共享变量时才需要使用它。
在纯粹的单线程程序里,volatile 关键字没有任何可见性或顺序性的实用价值,原因如下:
- 只有一条线程
- 不存在第二个 CPU 核心、第二个线程去读或写该变量,因此:
- 缓存一致性、可见性问题天然不存在;
- 指令重排序即使发生,也不会被其他线程“看到”,不影响程序单线程语义。
- 编译器/JVM 的as-if-serial规则
- 只要单线程执行结果不变,重排序就合法;volatile 插入的内存屏障在单线程里只是空耗性能。
- 仍然保留的语义
- 禁止把 volatile 变量缓存在寄存器(每次读写必须走内存)。
- 禁止与普通变量之间的重排序越过 volatile 访问。
- 这两点在单线程下既无收益也无危害,只是平白增加一次内存访问。