volatile不能保证count++原子性,因其仅保障单次读/写的可见性,无法约束“读—改—写”三步组合操作,多个线程可能同时读取相同旧值并写回相同结果,导致数据丢失。

volatile 不能保证 count++ 这类操作的原子性,根本原因在于:它只管“单次读”或“单次写”的可见性,不管“读—改—写”这一整套动作是否被完整、不中断地执行。
volatile 只保障单步操作的可见性
当一个变量被声明为 volatile,JVM 会确保:
- 每次读取该变量时,都从主内存加载最新值(而不是用线程本地缓存的旧值);
- 每次写入该变量后,立即刷新回主内存,并通知其他 CPU 缓存失效。
但这仅覆盖了“读”和“写”两个独立动作。而 count++ 实际上是三步组合:
- 从主内存读取
count的当前值(比如是 5); - 在 CPU 寄存器中执行加 1(得到 6);
- 把结果 6 写回主内存。
volatile 对第 2 步(计算)完全无约束,也无法阻止其他线程在第 1 步之后、第 3 步之前插入自己的读—改—写操作。
立即学习“Java免费学习笔记(深入)”;
多个线程可能同时读到同一个旧值
假设初始 count = 0,线程 A 和线程 B 几乎同时执行 count++:
- A 读到 0,B 也读到 0(volatile 确保它们都看到主内存里的 0);
- A 算出 1,B 也算出 1;
- A 写回 1,B 接着也写回 1。
最终结果是 1,而不是预期的 2。volatile 让它们“看到一样的起点”,却没让它们“错开执行路径”。这正是原子性缺失的典型表现。
字节码层面看,++ 操作天然非原子
编译后的 count++ 对应多条字节码指令,例如:
getstatic count // 读取 iconst_1 // 加 1 iadd // 执行加法 putstatic count // 写回
这些指令不是一条 CPU 原子指令,中间任意一步都可能被线程切换打断。volatile 不会对指令序列加锁或阻塞调度,所以无法打包保护整段逻辑。
正确做法:需要原子性就得用原子工具或锁
如果要安全实现并发自增,有明确替代方案:
- 用
AtomicInteger.incrementAndGet()—— 底层基于 CAS,保证读—改—写整体原子; - 用
synchronized包裹count++—— 用互斥锁强制串行执行; - 用
ReentrantLock显式加锁 —— 更灵活的同步控制。
volatile 在这里可以保留(比如配合 double-check 单例中的实例引用),但绝不能单独用来保护复合操作。


















