volatile不能保证复合操作原子性,因其仅保障单次读/写操作的可见性与有序性,无法控制“读—改—写”多步操作的整体执行,故无法防止竞态条件。

volatile 不能保证复合操作安全,根本原因在于它只管“单次读或写”的可见性与有序性,不管“读—改—写”这类多步动作是否被整体执行。
它只保单步,不保整条逻辑链
像 count++、list.add(x)、map.put(k, v) 这类操作,表面是一行代码,底层至少包含三步:
- 从主内存读取当前值到线程工作内存
- 在 CPU 寄存器中完成计算或判断(比如加 1)
- 把新值写回主内存
volatile 能确保第 1 步读到最新值、第 3 步写完立刻被别人看到,但它完全不干预第 2 步的执行时机,也不阻止其他线程在“读完还没写回”这个窗口期插进来——结果就是两个线程读到同一个旧值,各自算出相同的新值,先后写回,最终只生效一次。
它不提供互斥,也无法串行化执行
volatile 没有锁的概念,不会阻塞线程,也不会让多个线程排队执行。它只是给每次读写加了个“强制同步”标记:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 写 volatile 变量 → 立即刷到主内存
- 读 volatile 变量 → 强制从主内存加载,跳过本地缓存
这种机制对状态标志(如 volatile boolean shutdown = false)很高效,但一旦涉及“依赖当前值做决策再更新”,比如“余额大于 100 才扣款”,就毫无保护力——因为判断和扣款是两步,中间可能被其他线程改掉余额。
它不解决竞态条件的本质
竞态条件发生,是因为多个线程对共享且可变的状态进行非原子的交叉访问。volatile 只缓解了“看错值”的问题(可见性),却放任“做错事”的过程(原子性缺失)。真正要消除竞态,必须把整个业务逻辑包裹成一个不可分割的单元,要么用 synchronized 或 ReentrantLock 实现互斥,要么用 AtomicInteger 等原子类把关键操作压缩成一条 CAS 指令。
什么时候用 volatile 是对的
它适合那些“只写一次、多次读取”或“纯通知用途”的场景:
- 线程控制开关:volatile boolean running = true
- 单例对象的初始化完成标识(配合双重检查)
- 配置热更新中的开关位(如 volatile int logLevel)
这些都不需要“读—改—写”,只要值一变,大家立刻知道,就够了。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










