volatile无法防止覆盖丢失,因其仅保证单次读/写的可见性与有序性,不保证读-改-写复合操作的原子性;多个线程可能同时读取相同旧值并写回,导致部分更新被覆盖。

volatile 不能解决多线程并发写导致的覆盖丢失问题。
为什么 volatile 无法防止覆盖丢失
覆盖丢失(Lost Update)本质是复合操作的非原子性问题,典型如 i++、count += 1 或 flag = flag || newValue。这类操作在底层分为三步:
- 读取当前值(read)
- 在本地计算新值(modify)
- 写回结果(write)
volatile 只保证单次读或单次写的可见性与有序性,但不保证这三步整体不可分割。多个线程可能同时读到相同旧值,各自计算后写回,最终只有一个结果被保留,其余被覆盖。
一个直观的例子
假设两个线程同时执行 counter++,初始值为 0:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 线程A读取 counter = 0
- 线程B读取 counter = 0(此时 volatile 已确保它看到最新值,但仍是 0)
- 线程A计算 0+1=1,写入 counter = 1
- 线程B计算 0+1=1,写入 counter = 1
最终结果是 1,而非预期的 2 —— 这就是覆盖丢失。volatile 让双方都“看见”了初始值,却没让整个 ++ 操作串行化。
真正能解决覆盖丢失的方案
必须引入原子性保障,常见方式有:
-
使用 AtomicInteger 等原子类:其
incrementAndGet()底层通过 CAS(Compare-And-Swap)实现无锁原子更新 - 用 synchronized 同步临界区:把读-改-写逻辑包在同一个锁内,强制互斥执行
- 使用 ReentrantLock 或显式 Lock:提供更灵活的锁控制能力
- 避免共享可变状态:改用不可变对象、线程局部变量(ThreadLocal)或消息传递模型
volatile 的合理定位
volatile 的正确用途是做状态通知或安全发布,例如:
- 开关标志:
private volatile boolean running = true; - DCL 单例中的 instance 引用(防止构造过程重排序)
- 作为“写一次,读多次”的配置项(如初始化完成标志)
它不是同步工具,而是可见性与有序性的“信使”。想防覆盖,得靠原子操作或锁机制。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










