atomicstampedreference 的 stamp 是完整 int(32 位),非15位;必须每次 cas 前重新读取当前 stamp 并递增,不可硬编码或缓存;其适用场景需严格判断 aba 是否真实存在,否则应优先用 atomicreference 或业务状态机。

AtomicStampedReference 的版本号不是 15 位,而是 int(32 位)
很多人看到“15 位”是误读了某些旧文档或混淆了 AtomicMarkableReference 的标记位(单 bit)——AtomicStampedReference 的 stamp 是完整的 int,范围是 -2³¹ 到 2³¹−1。它没有“15 位限制”,也不存在刻意截断或掩码操作的默认行为。
如果你在压测中观察到 stamp 溢出(比如从 Integer.MAX_VALUE 回绕到 Integer.MIN_VALUE),那说明你的业务更新频率极高、持续时间极长,或者版本号递增逻辑不合理(比如非必要地频繁 bump)。这不是 API 设计缺陷,而是使用方式越界。
compareAndSet 必须基于刚读到的 stamp 计算 newStamp
这是最常踩的坑:把 stamp 当作全局单调计数器硬编码递增,导致多线程下多个线程基于同一个旧 stamp 值执行 +1,结果并发写入相同新 stamp,破坏了“每次修改必留痕”的前提。
- 错误写法:
state.compareAndSet("A", "B", 0, 1)—— 初始化后就硬编码版本号,第一次调用就可能失败 - 正确写法:先调用
state.get(stampHolder)拿当前值和当前stamp,再用stampHolder[0] + 1作为newStamp - 必须在每次 CAS 尝试前重新读取,不能缓存
stamp超过一次 CAS 周期
高并发下避免 stamp 竞争的关键是减少重试粒度
AtomicStampedReference 本身无锁,但业务逻辑若包裹在 while 循环里反复 get → compareAndSet,而每次 get 都要 volatile 读 pair 对象,高频场景下会成为瓶颈。这不是 stamp 位数问题,而是访问模式问题。
- 不要在循环里只改
newStamp却不重新读reference:上一轮失败后,值可能已变,继续用旧expectedRef会导致无限失败 - 如果业务允许,优先用
AtomicReference+ 业务层状态机(比如用枚举区分 INIT/PROCESSING/DONE),比强依赖 stamp 更轻量 - 真需要 stamp 语义时,可考虑将 stamp 和业务状态打包进一个不可变对象,用
AtomicReference替代,避免双字段原子更新开销(但需自行保证 stamp 递增逻辑)
ABA 问题是否真的存在,取决于你的数据结构语义
不是所有引用变更都怕 ABA。AtomicStampedReference 解决的是“值相同但中间被复用”的风险,典型如无锁栈的 head 节点被弹出 → 回收 → 重用为新节点 → 再压入。这时即使值一样,内存地址或逻辑身份已不同。
- 如果你只是维护一个共享配置字符串(如
"RUNNING"→"STOPPED"),且中间不会“退回原值”,那AtomicReference就够用 - 如果你的引用指向堆外资源、数据库行、或会被池化复用的对象,才真正需要 stamp 校验
- 过度使用
AtomicStampedReference反而掩盖设计问题:比如本该用唯一 ID 或时间戳标识状态变迁,却依赖值相等性做判断
真正难的不是理解 stamp 怎么用,而是判断“我的场景里,ABA 是否会导致逻辑错误”。很多团队加了 stamp 后性能下降、代码变复杂,最后发现根本没发生过真实 ABA——因为业务天然避免了值回滚。










