根本原因在于volatile不保证原子性,而count++是读-改-写复合操作;它仅保障可见性与禁止重排序,无法防止多线程并发执行时的丢失更新。

Java 高并发计数器使用 volatile 出现数据错误,根本原因在于:volatile 只保证可见性和有序性,不保证原子性。而计数器的典型操作(如 count++)是复合操作,天然不具备原子性。
volatile 能做什么?—— 两个确定性保障
-
可见性:一个线程修改了
volatile int count,其他线程后续读取能立即看到新值(强制从主内存读,写后立即刷回主内存); -
禁止重排序:JVM 和 CPU 不会对
volatile变量的读写做指令重排,比如在单例双重检查中防止“引用赋值早于对象初始化”。
但它不做以下三件事:
- 不锁住变量,不阻止多个线程同时进入读-改-写流程;
- 不把
count++这类操作变成一条不可分割的 CPU 指令; - 不建立多个 volatile 变量之间的 happens-before 关系(比如
if (flag && count > 0)中,flag可见,但count不一定同步可见)。
为什么 count++ 会出错?
count++ 在字节码层面等价于三个步骤:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 读取
count的当前值(比如 0); - 在本地计算
0 + 1 = 1; - 将结果 1 写回
count。
即使每个步骤都作用于 volatile 变量,整个过程仍可被其他线程打断。例如:
| 时间 | 线程 A | 线程 B |
|---|---|---|
| t1 | 读到 count = 0
|
|
| t2 | 读到 count = 0
|
|
| t3 | 计算得 1,写入 1
|
|
| t4 | 计算得 1,写入 1
|
最终 count = 1,而非预期的 2 —— 这就是典型的丢失更新(Lost Update)。
正确替代方案(按场景选)
- ✅ 单一整型计数、无复杂逻辑:用
AtomicInteger(底层 CAS + volatile,原子性有保障); - ✅ 需要更高吞吐(如百万级 QPS 计数):考虑
LongAdder(分段累加,比AtomicInteger更低竞争); - ✅ 计数需配合其他状态或业务逻辑:用
synchronized或ReentrantLock控制临界区; - ❌ 不要用
volatile+ 手动加锁模拟、也不要用volatile boolean包裹非原子操作来“假装安全”。
常见误用强化提醒
-
volatile List<t> list = new ArrayList()</t>:只保证list引用本身变更可见,list.add(x)的内部修改仍不安全; -
volatile Boolean running:自动拆箱可能触发NullPointerException,且null值无同步语义; -
if (running && count > 0):running可见,但count的读取不与它构成 happens-before,不能确保看到最新count。
不复杂但容易忽略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










