volatile不能保证复合操作原子性,因其仅确保单次读写可见性与有序性,而count++等操作含读-改-写三步,多线程交错执行会导致结果丢失。

因为复合操作天然不具备原子性,而共享变量在多线程环境下一旦被多个线程交错执行,就会导致结果错乱、数据丢失或状态不一致——这不是小概率事件,而是必然发生的并发缺陷。
复合操作本质是多步非原子动作
比如 count++、list.add(x)、map.put(k, v) 等看似简单的一行代码,在底层至少包含“读取→计算/判断→写入”三个独立步骤。这些步骤可能被线程切换打断:
- 线程 A 读取 count = 100,准备加 1;
- 尚未写回前,CPU 切换到线程 B,B 同样读取到 count = 100;
- A 和 B 分别算出 101,再先后写回主内存;
- 最终结果仍是 101,而非预期的 102 —— 一次更新被彻底覆盖。
共享变量 + 多线程 = 竞态条件温床
只要变量满足两个条件:(1)被多个线程访问,(2)值会改变(即“共享且可变”),就构成竞态条件风险点。大厂系统常面临高并发请求、定时任务、异步回调、多实例共享缓存等场景,这类变量极易暴露:
- 订单号生成器的自增序列
- 用户登录态计数器(如当前在线人数)
- 配置热更新中的开关标志位
- RPC 调用统计中的 success/fail 计数
不加保护时,轻则统计失真,重则引发资金错误、权限越界、状态错乱等线上事故。
原子锁提供三重保障,缺一不可
以 synchronized 或 ReentrantLock 为代表的原子锁,不只是“串行化执行”,它同时解决并发三大核心问题:
- 原子性:确保临界区内所有操作要么全执行,要么全不执行,不被中断
- 可见性:锁释放时强制刷新工作内存到主内存,保证下一个获取锁的线程看到最新值
- 有序性:内置内存屏障,禁止编译器和 CPU 对临界区内外指令重排序
为什么不全用 volatile 或 final?
volatile 只能保证单次读/写的可见性与有序性,但无法保障复合操作的原子性;final 只适用于初始化后不再修改的常量。它们都不适用于需要“读-改-写”逻辑的场景。而原子类(如 AtomicInteger)虽可替代部分简单计数,但在涉及多个变量协同、复杂业务逻辑(如“余额充足才扣款+记日志+发消息”)时,仍需锁来包裹整个事务边界。
规范不是教条,而是血泪经验的凝练。加锁不是为了“看起来安全”,而是让并发行为变得可预测、可验证、可回溯。











