核心思路是引入版本号戳实现“值+状态”双校验:atomicstampedreference将引用与整型stamp封装为原子pair,compareandset要求预期值和预期stamp同时匹配,使a→b→a后stamp已变导致cas失败,从而暴露而非阻止aba,确保状态一致性。

CAS 操作中解决 ABA 问题,核心思路是:不能只比“值”,还得记录“有没有被改过”。Java 提供了带版本标记的原子引用类型,让每次修改都附带一个可追踪的标识,从而打破“值相同就等于没变”的误判逻辑。
为什么 AtomicInteger 会出 ABA 问题
AtomicInteger 的 compareAndSet 只校验当前值是否等于预期值。如果变量从 100 → 150 → 100,线程仍会认为“没动过”,直接更新成功。这在某些业务场景下是危险的——比如链表节点复用、资源状态机跳转、余额变更中间态等,表面值一致,但背后语义已不同。
用 AtomicStampedReference 带版本号校验
它把值和一个整型“戳记”(stamp)绑定在一起,CAS 成功需同时满足:值相等 且 版本号相等。每次 set 或 weakCompareAndSet 都可递增 stamp,确保中间修改必然导致 stamp 不匹配。
- 初始化时传入初始值和起始版本号,如
new AtomicStampedReference<integer>(100, 0)</integer> - 执行 CAS 时必须提供旧值和对应旧版本号:
compareAndSet(expectedRef, newRef, expectedStamp, newStamp) - 获取当前值和版本号用
get(int[] stampHolder),数组用于传出 stamp 值 - 版本号不强制自增,但实践中建议每次修改都 +1,避免重复或回绕
用 AtomicMarkableReference 标记是否被修改过
适用于只需区分“是否被动过”两类状态的场景。它用一个布尔标记(mark)代替数字版本号,更轻量。
- 构造:传入初始值和初始标记(如
false表示未被修改) - CAS 方法为
compareAndSet(E expectedRef, E newRef, boolean expectedMark, boolean newMark) - 适合做简单状态切换,比如“可用/不可用”、“已初始化/未初始化”
- 缺点是无法区分多次修改,只记录“改过”或“没改过”
业务层规避:避免复用同一地址或值
在对象引用层面,ABA 更隐蔽——比如链表节点被移除后又被新节点复用同一内存地址(C++常见),或 Java 中对象被 GC 后又新建同内容对象被误认为原对象。应对方式包括:
- 禁用节点复用机制,每次操作都分配新对象
- 在关键对象中加入唯一 ID 或创建时间戳字段,CAS 时一并校验
- 使用不可变对象(Immutable Object),值变即换新实例,天然规避 ABA
- 对资源类操作加业务锁或状态检查,例如转账前先查账户冻结状态
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











