volatile不能保证i++原子性,因其仅确保单次读/写直通主内存,而i++包含读取、计算、写回三个未锁定的步骤,导致多线程下出现丢失更新。

volatile 不能保证 i++ 这类复合操作的原子性,根本原因在于:它只干预“单次读”或“单次写”的行为,而 i++ 在语义和执行层面天然包含三个不可分割但又**未被锁定**的步骤。
volatile 只保障单次读/写的原子性
Java 内存模型(JMM)规定,对 volatile 变量的每次 读操作(如 getstatic)都会直接从主内存加载最新值;每次 写操作(如 putstatic)都会立即刷回主内存。这确保了单次读或写本身不会被拆开、不会被缓存延迟——也就是“单次读/写具有原子性”。
但它对“读—改—写”这种跨多个指令的动作完全不设防。
i++ 是三步非原子动作,volatile 不参与中间过程
i++ 表面简洁,底层至少对应三条字节码指令:
立即学习“Java免费学习笔记(深入)”;
- 读取当前值(
getstatic i)→ 从主内存加载到线程工作内存 - 执行加 1(
iadd)→ 在工作内存中计算 - 写回新值(
putstatic i)→ 把结果刷回主内存
volatile 仅强制第一步和第三步直通主内存,第二步(计算)在线程私有工作内存中进行,且两步之间无任何同步机制。这就为竞态留下明确窗口。
典型丢失更新场景:两个线程同时“看到同一个旧值”
假设 volatile int i = 5;线程 A 和 B 同时执行 i++:
- A 读到 i = 5,准备加 1
- B 也读到 i = 5(此时 A 尚未写回)
- A 计算得 6,写回主内存
- B 计算得 6,也写回主内存
最终 i = 6,而非预期的 7 —— 一次累加被覆盖,这就是“丢失更新”。这不是偶发问题,而是必然发生的数据竞争。
真正能保证原子性的替代方案
若需安全执行 i++,必须用能锁住整个“读—改—写”流程的机制:
-
AtomicInteger.incrementAndGet():底层基于 CAS(Compare-And-Swap),硬件级保证三步一体 -
synchronized块包裹 i++:靠监视器锁串行化访问 - 显式锁(
ReentrantLock):提供更灵活的加锁控制
volatile 在这里唯一能做的,是让其他线程更快看到最终结果,但它绝不负责“不让别人插队”。


















