volatile无法保障复合操作的原子性,因其仅保证单次读/写原子性,而count++等“读—改—写”操作会被拆解为多步,导致丢失更新;synchronized通过互斥锁将代码块变为不可分割的临界区,确保复合操作原子性。

volatile 无法保障复合操作的原子性
volatile 只保证单个读或写操作的原子性,比如 flag = true 或 if (done) 这类简单赋值或读取。但它对 count++、value += 1、list.add(x) 这类“读—改—写”三步组合操作完全无保护作用。
原因在于:这些操作在 JVM 层面会被拆解为多个指令(读取当前值 → 在寄存器中计算 → 写回新值),而 volatile 不阻止其他线程在这三步之间插入执行——结果就是丢失更新。即使变量本身被声明为 volatile int count,最终结果仍可能远小于预期。
synchronized 能完整保障复合操作的原子性
synchronized 通过互斥锁机制,把一段代码逻辑整体变成“不可分割的临界区”。只要操作被包裹在 synchronized 块或方法内,JVM 就确保:
- 同一时刻最多一个线程能进入该区域
例如 count++ 放在 synchronized 方法中,就等效于“锁定→读→加1→写→解锁”整个流程由单一线程独占完成,彻底避免竞态条件。
常见误用场景与识别要点
以下情况必须用 synchronized(或 AtomicInteger 等原子类),不能依赖 volatile:
- 计数器累加(i++、sum += x)
- 集合增删改(ArrayList.add()、HashMap.put())
- 状态切换伴随副作用(如 “若未初始化则创建实例”,需双重检查 + volatile + synchronized 配合)
- 多个变量需保持一致性(如 balance 和 lastTransactionTime 要同步更新)
判断依据很简单:只要操作涉及“不止一次内存访问”,且中间状态对外可见或影响逻辑,就不是 volatile 能覆盖的范围。










