无锁栈易触发ABA问题是因为CAS仅比较引用值,当节点A被弹出后复用为新节点时,表面值相同但逻辑身份不同,导致CAS误成功;AtomicStampedReference通过绑定递增版本戳确保每次状态变更stamp唯一,使复用节点因stamp不匹配而CAS失败,从而杜绝ABA。

AtomicStampedReference 在无锁 Stack 中防止 ABA 节点复用,核心是让每次引用变更都绑定唯一递增的版本戳,使“值相同但身份不同”的节点无法通过 CAS 校验。
为什么无锁栈特别容易触发 ABA
无锁栈依赖 CAS 修改 top 引用。当一个节点 A 被弹出(top 从 A 变为 B),又被回收、重用为新压入的节点(内容相同、地址可能复用),此时 top 表面仍指向 A —— 值没变,但逻辑上已是另一个生命周期的 A。线程若仍持旧快照尝试 CAS(A→X),就会误成功,破坏栈结构一致性。
AtomicStampedReference 的关键改造点
- 每个 push/pop 都必须同时更新引用和 stamp:不能只改节点,必须让 stamp 随每次有效变更递增(哪怕内容不变)
-
读取必须成对获取:调用
get(int[])或分别调用getReference()和getStamp(),确保引用和 stamp 来自同一原子快照,避免错位 - 循环重试中 stamp 必须动态刷新:不能在循环外固定 stamp 初始值后硬加 1;每次 CAS 失败后,都要重新读最新 stamp 再参与下一轮比较
- 节点复用本身不禁止,但复用后 stamp 必须不同:即使把刚弹出的 Node 对象立即重用于 push,也必须赋予新 stamp,否则等于主动制造 ABA
典型 push 操作写法示意
以压入新节点 newTop 为例:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- 声明
int[] stampHolder = {0} - 循环体内:
Node currentTop = stack.get(stampHolder)→ 同时拿到当前 top 和对应 stamp - 构造新栈顶:
newTop.next = currentTop - 执行 CAS:
stack.compareAndSet(currentTop, newTop, stampHolder[0], stampHolder[0] + 1) - 失败则继续循环,stampHolder 自动被下次
get()更新
出栈 pop 的注意事项
pop 需要原子地读取 top 并更新为 next,同时推进 stamp:
立即学习“Java免费学习笔记(深入)”;
- 同样用
get(int[])获取当前 top 和 stamp - 若 top 为 null,直接返回 null
- 否则计算新 top:
Node next = top.next - CAS 更新:
compareAndSet(top, next, currentStamp, currentStamp + 1) - 成功才返回 top;失败则重试
不复杂但容易忽略
版本号不是装饰,它是逻辑时间戳。只要 stamp 在每次真实状态跃迁时严格递增,哪怕节点对象被复用,CAS 也会因 stamp 不匹配而拒绝操作——这才是堵住 ABA 漏洞的本质机制。

















