CAS不防ABA问题,因其仅比对值是否相等而无法感知中间修改;解决关键是改用带状态标记的CAS变体,如AtomicStampedReference(加版本戳)或AtomicMarkableReference(加布尔标记),确保值与标记同时匹配才成功。

CAS 乐观锁本身不防 ABA 问题,它只比对值是否相等,无法感知中间是否被修改过。防止 ABA 引发的数据异常,关键不是“让 CAS 自己变聪明”,而是**改用带状态标记的 CAS 变体**,让一次“值没变”的判断,同时包含“过程也没被干扰”的含义。
为什么普通 CAS 容易出错
比如一个链表节点被回收又复用、一个账户余额被先扣再充回原数——表面看值还是 A,但背后的对象或业务语义已不同。CAS 成功更新,却跳过了本该检测到的中间状态变化,导致逻辑错误(如节点丢失、重复扣款)。
核心解法:给值加版本戳
Java 提供了 AtomicStampedReference 和 AtomicMarkableReference 两类工具,它们把“值”和“辅助标记”打包成原子单元:
- AtomicStampedReference:用整型 stamp 当版本号,每次修改值时主动递增 stamp;CAS 要求值和 stamp 同时匹配才成功
- AtomicMarkableReference:用布尔 mark 标记状态(如“是否已被删除”),适合二态场景
这样即使值从 A→B→A,stamp 也从 1→2→3,线程拿着旧 stamp=1 去 CAS,必然失败,从而避免误判。
立即学习“Java免费学习笔记(深入)”;
实际使用注意点
光用 AtomicStampedReference 不够,关键在怎么用:
- 每次调用
compareAndSet时,必须传入当前获取到的 stamp 值,不能缓存旧 stamp - 更新值的同时,要同步递增 stamp(通常用
getStamp() + 1),不能漏掉这步 - 不是所有场景都需要防 ABA:纯数值计数(如 AtomicInteger)极少出现真实 ABA;但涉及对象引用、链表操作、资源池管理时,必须考虑
替代思路:避免地址复用
在底层数据结构设计中,还可以从源头减少 ABA 概率:
- 使用对象池时,不直接复用对象实例,而是重置内部状态并保留唯一 ID
- JVM 堆上对象地址一般不复用,所以纯 Java 引用场景 ABA 概率极低;但 ConcurrentLinkedQueue 等并发容器仍内置 stamp 机制,因为它们操作的是自管理的节点内存


















