volatile能保证变量读取的可见性但不保证原子性;它通过内存屏障确保写操作立即刷回主存、读操作强制从主存加载,并禁止指令重排,但无法防止i++等读-改-写操作的竞态条件。

volatile 在多线程并发修改同一个变量时,**能保证每次读取都看到最新值,但无法防止多个线程同时读-改-写导致的丢失更新**。它解决的是“看不看得见”的问题,不是“能不能安全改”的问题。
volatile 能做到的事:强制可见、禁止重排
当一个线程写 volatile 变量,其他线程后续读该变量时,一定能读到刚写入的值(或之后的某个新值),不会卡在旧缓存里。这是因为:
- 写操作会插入 Store 屏障:把修改立即刷回主内存,并让其他 CPU 缓存行失效
- 读操作会插入 Load 屏障:跳过工作内存缓存,强制从主内存加载最新值
- 编译器和处理器不会把 volatile 读写与周边普通指令随意重排序
volatile 做不到的事:不保证原子性
对变量做 自增(i++)、条件写(if (x == 0) x = 1) 这类复合操作时,volatile 无能为力。因为 i++ 实际包含三步:
- 读 i 的当前值(volatile 保证读到最新)
- 在本地计算 i + 1(这步不涉及 volatile)
- 写回新值(volatile 保证这次写对别人可见)
如果两个线程几乎同时执行 i++,可能都读到 i=5,各自算出 6,再都写回 6——最终结果还是 6,而不是预期的 7。这就是典型的“竞态条件”,volatile 不提供锁或 CAS 那样的原子保障。
典型误用场景:用 volatile 保护计数器
下面这段代码看似安全,实则危险:
private static volatile int counter = 0;
// 多个线程同时调用
public static void increment() {
counter++; // ❌ 非原子操作,volatile 无法挽救
}
正确做法是换用 AtomicInteger 或加 synchronized:
-
new AtomicInteger().incrementAndGet()—— 底层用 CAS 保证原子性 - 用 synchronized 包裹整个 increment 逻辑 —— 串行化执行
适合用 volatile 的典型场景
volatile 最适合用于状态标志、一次性通知等“纯读写”且无需中间计算的场合:
- 线程启停开关:
private volatile boolean running = true; - 初始化完成标记:
private volatile boolean initialized = false; - 单次发布对象引用(配合双重检查锁单例):
private static volatile Singleton instance;
这些场景只依赖“写后立即被读到”,不涉及多次读写交织的复合逻辑。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











