volatile仅保证单次读/写操作的可见性与有序性,不保证count++等读-改-写三步复合操作的原子性,故无法防止丢失更新。

因为 count++ 是一个**三步复合操作**,而 volatile 只能保证**单次读或写操作的可见性与有序性**,不保证多个操作组合起来的原子性。
volatile 能保证什么?
volatile 关键字主要提供两个语义:
- 可见性:当一个线程修改了 volatile 变量的值,其他线程能立即看到最新值(不会因 CPU 缓存未刷新而读到旧值);
- 禁止指令重排序:JVM 和 CPU 不会对 volatile 读写与其他操作随意重排(如防止初始化对象时构造函数执行完前就将 this 引用泄露出去)。
但它不提供原子性保障——对变量的读-改-写(read-modify-write)这类多步操作,它无能为力。
count++ 实际做了哪三件事?
看似简单的一行代码:count++,在 JVM 层面对应的是三个不可分割但又各自独立的步骤:
立即学习“Java免费学习笔记(深入)”;
- 从主内存(或当前线程缓存)读取 count 的当前值;
- 在 CPU 寄存器中对该值 加 1;
- 将结果 写回 count 变量所在内存位置。
即使每个读、写都用了 volatile,中间的“加 1”仍可能被其他线程打断。例如:
线程 A 读到 count = 5 → 线程 B 也读到 count = 5 → A 算出 6 并写入 → B 算出 6 并写入 → 最终 count = 6,而不是预期的 7。
为什么 synchronized 或 AtomicInteger 可以?
它们通过不同机制实现了真正意义上的原子性:
-
synchronized:用锁强制同一时刻只有一个线程能执行整个count++过程,把三步变成一个临界区; -
AtomicInteger.incrementAndGet():底层基于 CAS(Compare-and-Swap)指令,在硬件层面保证“比较并更新”是一条原子 CPU 指令(如 x86 的lock xadd),失败则重试。
这两种方式都覆盖了“读-改-写”的完整逻辑,而 volatile 只能保其中两步(读和写)各自可见,无法绑定三步为一个整体。
volatile 适合用在哪些场景?
它适合那些**天然具备原子性的单次操作**,比如:
- 布尔标志位:
volatile boolean isRunning = true;,赋值和读取本身是原子的; - 对象引用的发布:
volatile MyService instance = new MyService();,避免其他线程看到半初始化的对象; - 作为状态信号配合其他同步机制(如与
wait/notify配合使用)。
但只要涉及“先读后写+中间计算”,volatile 就不够用了。


















