atomicstampedreference通过将值与版本号封装为原子pair并要求二者同时匹配来解决aba问题,使线程能感知“a→b→a”历史变更而非仅依赖值是否相同。

CAS 操作的 ABA 问题不是值错了,而是“值没变、历史变了”导致的状态误判。版本号机制不阻止 A→B→A 的发生,而是让线程明确感知到它发生过——关键在于把值和版本号打包成不可分割的原子单元,两者必须同时匹配,CAS 才能成功。
AtomicStampedReference 怎么绑定值与版本号
它内部维护一个 Pair
- get() 返回的是 new Pair(reference, stamp),避免拆包时被其他线程中途修改
- compareAndSet() 必须传入旧值、新值、旧 stamp、新 stamp,一次完成二者更新
- CAS 底层比对的是整个 pair:当前 reference == 预期 reference 且 当前 stamp == 预期 stamp
为什么只比值不行,加 stamp 就能识别 ABA
假设链表头节点从 A → B → A,表面值未变,但中间结构已破坏。若仅用 AtomicReference,线程1读到 A 后被挂起,线程2完成 A→B→A,线程1恢复后 CAS(A→X) 会成功——它完全不知道 A 已被弹出又放回。
而 AtomicStampedReference 中,每次结构性变更(如 pop/push、指针重连)都必须主动更新 stamp。只要 A 被动过,stamp 就从 0→1→2。线程1拿着旧 stamp=0 去比,当前 stamp 已是 2,CAS 直接失败,强制其重新读取最新值和 stamp 再决策。
使用中必须注意的业务约束
stamp 本身无业务含义,只作“修改指纹”,但它的管理完全依赖业务逻辑:
- 每次状态变更(哪怕值不变)都必须更新 stamp,通常配合 AtomicInteger.incrementAndGet()
- 绝不复用旧 stamp,也不跨操作共享同一 stamp 值
- 每次 compareAndSet 的旧 stamp 必须来自本次 get() 获取的结果,不能缓存或硬编码
- 推荐用自增 int,避免仅用 0/1 交替;高并发长期运行场景可考虑用 long 防溢出
典型安全写法示例
正确模式是“读–改–比”三步闭环:
- 每次调用 ref.get(stampHolder) 都新建 int[] 或确保 stampHolder 未被污染
- 拿到 oldVal 和 oldStamp 后立即构造新值和 newStamp = oldStamp + 1
- 失败则重试,常包裹在 while 循环中,不依赖 stampHolder 复用
它解决的是可感知的 ABA,不是消灭 ABA。本质是让乐观锁更“谨慎”:不阻塞,但拒绝轻信。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











