volatile不能保证复合操作原子性,根本原因在于它仅保障单次读/写操作的可见性与有序性,而i++等“读—改—写”操作会被jvm拆解为多步指令,线程可在此间交错执行,导致丢失更新。

volatile 不能保证复合操作的原子性,根本原因在于它只保证可见性和有序性,不保证操作本身的不可分割性。
volatile 的三大作用:可见性、禁止重排序,但不包括原子性
Java 中 volatile 关键字主要解决两个问题:
- 可见性:一个线程修改了 volatile 变量,其他线程能立即看到最新值(通过强制写回主内存、强制读取主内存实现);
- 禁止指令重排序:编译器和处理器不会将 volatile 读写与其他读写随意重排(有 happens-before 规则约束);
- 但它不提供原子性:比如 i++ 看似一条语句,实际包含“读取 i → 计算 i+1 → 写回 i”三个步骤,volatile 无法阻止多个线程在这三步之间穿插执行。
i++ 在多线程下为什么出错?
假设初始 i = 0,两个线程同时执行 i++:
- 线程 A 读取 i = 0;
- 线程 B 也读取 i = 0(此时 i 还没更新);
- A 计算 0+1 = 1,写回 i = 1;
- B 计算 0+1 = 1,写回 i = 1;
最终结果是 i = 1,而不是预期的 2。这个错误不是因为看不到新值(volatile 能保证看到),而是因为“读-改-写”这整套动作被并发打断了。
volatile 适合哪些场景?
它适用于“单纯读写”或“状态标志”类操作,例如:
- 布尔开关:volatile boolean running = true;,一个线程设为 false,其他线程能立刻感知并退出循环;
- 单次写入、多次读取的配置项:volatile String config = "prod";;
- 作为安全发布(safe publication)的辅助手段,配合 final 或锁使用。
只要不涉及“读取旧值 → 基于旧值计算 → 写入新值”这类依赖中间状态的复合操作,volatile 就是安全的。
那 i++ 这种操作该怎么保证正确?
必须引入原子性保障机制:
- 用 AtomicInteger:它内部用 CAS(Compare-and-Swap)实现 i.incrementAndGet(),底层由 CPU 指令保证读-改-写原子性;
- 加 synchronized 块或方法:让整个 i++ 操作串行执行;
- 用 ReentrantLock 控制临界区;
- 避免共享可变状态——更推荐的设计方向,比如用不可变对象 + 函数式风格减少竞争。
注意:synchronized 和 AtomicXxx 都比 volatile “重”,但该用的时候不能图省事。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











