aba问题本质是值未变但状态已变,atomicstampedreference通过戳记+值双重校验解决,需手动递增戳记;atomicmarkablereference适用于单次状态翻转;业务层可结合地址唯一性、version乐观锁或改用显式锁规避。

Java 中 CAS 操作的 ABA 问题,本质是“值未变但状态已变”——线程看到共享变量还是原来的值 A,就认为没人动过它,于是执行了 CAS 更新;可实际上该值曾被其他线程改成 B 又改回 A。这种表面一致、中间篡改的情况,会让基于状态逻辑的并发操作出错(比如链表节点回收、资源标记等)。解决的核心思路是:不能只比值,还要记录“这个值被改过几次”。
用 AtomicStampedReference 带版本号做双重校验
这是最常用、最直接的解决方案。它把一个整型“戳记(stamp)”和引用值绑定在一起,每次 CAS 都要同时验证值和戳记是否匹配。
- 初始化时指定初始值和初始戳记(如
new AtomicStampedReference(100, 1)) - 调用
compareAndSet(expectedRef, newRef, expectedStamp, newStamp),四个参数缺一不可 - 每次成功修改后,必须手动递增戳记(比如
stamp.getAndIncrement()),否则多次修改会复用同一戳记,失去防护意义 - 适用于 Integer、String 等不可变对象,也适用于自定义对象引用
用 AtomicMarkableReference 标记是否被修改过
当不需要精确计数、只需区分“是否被动过”,可用这个轻量级替代方案。它用一个布尔标记(mark)代替整型戳记。
- 构造时传入初始值和初始标记(如
new AtomicMarkableReference("data", false)) - CAS 方法为
compareAndSet(expectedRef, newRef, expectedMark, newMark) - 适合场景:比如标记某个资源是否已被释放、某个任务是否已触发过,不关心具体改了多少次
- 注意:它无法防止“false → true → false”导致的二次 ABA,仅适合单次状态翻转逻辑
业务层规避:避免依赖“值不变”推断“状态未变”
有些场景其实不需要技术兜底,而是设计上绕开风险。
- 对链表、栈、队列等结构操作时,不单靠节点值判断是否可重用,而是结合节点地址唯一性 + 时间戳 / 版本字段
- 在数据库或分布式缓存中,用带 version 字段的乐观锁代替纯值比对
- 对计数类场景(如 AtomicInteger),若只是累加/统计,ABA 不影响结果正确性,可不处理;但若该计数用于控制状态流转(如“只有从 0→1 才允许启动”),就必须加版本
- 关键逻辑中,把“读取-判断-更新”拆成原子方法调用,或改用显式锁(如 ReentrantLock)保障临界区完整性
慎用 Unsafe 自行实现版本控制
虽然 Unsafe 提供了底层 CAS 能力,也能配合自定义字段实现戳记逻辑,但不推荐。
- Unsafe 被限制访问,反射获取易出错且不兼容未来 JDK 版本
- 手动管理戳记容易遗漏递增、溢出未处理、多字段更新非原子等问题
- JUC 已提供成熟封装(AtomicStampedReference),稳定、测试充分、语义清晰
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











