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></reference>,所有 CAS 操作都要求 reference 和 stamp 同时匹配。
- 构造时需传入初始引用和初始版本号,例如:
new AtomicStampedReference(new Node(1), 0) -
compareAndSet(expectedRef, newRef, expectedStamp, newStamp)是核心方法,四个参数缺一不可 - 调用前必须先用
get(int[] stampHolder)获取当前引用和版本号,因为 stamp 是输出参数
一个典型修复 ABA 的代码片段
以无锁栈的 pop 操作为例:
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 的语义设计和更新时机。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











