AtomicStampedReference通过引入版本戳(stamp)解决ABA问题,CAS操作需同时校验引用和stamp;普通AtomicReference仅比对引用值,导致X→Y→X时误判成功;其核心方法compareAndSet要求四个参数,且stamp需手动管理并防溢出。

Java 中用 AtomicStampedReference 解决 ABA 问题,核心是给引用加上版本戳(stamp),让 CAS 操作不仅比对值,还要比对版本号。即使引用被改回原值,只要中间发生过修改(stamp 变化),CAS 就会失败,从而避免误判。
为什么需要 AtomicStampedReference
普通 AtomicReference 的 CAS 只检查引用是否等于预期值。如果线程 A 读到值为 X,中途被挂起;线程 B 把 X → Y → X 改了两次,A 恢复后仍看到 X,CAS 就会成功——但实际状态已不同,这就是 ABA 问题。典型场景如栈的弹出/压入、无锁队列等。
AtomicStampedReference 的基本用法
它内部维护一个 Pair<reference, stamp>,所有 CAS 操作都要求 reference 和 stamp 同时匹配。
- 构造时需传入初始引用和初始版本号,例如:
new AtomicStampedReference<>(new Node(1), 0) -
compareAndSet(expectedRef, newRef, expectedStamp, newStamp)是核心方法,四个参数缺一不可 - 调用前必须先用
get(int[] stampHolder)获取当前引用和版本号,因为 stamp 是输出参数
一个典型修复 ABA 的代码片段
以无锁栈的 pop 操作为例:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
立即学习“Java免费学习笔记(深入)”;
public Node pop() {
int[] stamp = new int[1];
Node current;
Node next;
do {
current = ref.get(stamp); // 同时拿到引用和当前 stamp
if (current == null) return null;
next = current.next;
// 只有当引用没变、且 stamp 也没变时,才尝试更新
} while (!ref.compareAndSet(current, next, stamp[0], stamp[0] + 1));
return current;
}
注意:每次成功 CAS 后,stamp 都要递增(哪怕逻辑上只是“移除一个节点”),确保中间任何修改都会导致 stamp 不一致。
使用时的关键细节
stamp 不是自动递增的,必须由你手动管理。常见做法是每次 CAS 成功就 +1,或用时间戳、随机数等避免重复。
- stamp 类型是
int,要注意溢出(Integer.MAX_VALUE + 1 变成 Integer.MIN_VALUE),生产环境建议用long自定义封装,或限制操作次数 - 不能只靠 stamp 判断是否发生修改——必须和引用一起校验,单独 stamp 变化无意义
- 如果多个逻辑共享同一个
AtomicStampedReference,需协调 stamp 更新策略,避免误冲突
不复杂但容易忽略:ABA 的本质不是“值变回来”,而是“状态不可信”。AtomicStampedReference 提供的是一个轻量级的状态标记机制,用好它,关键在 stamp 的语义设计和更新时机。

















