AtomicStampedReference通过绑定版本戳解决ABA问题:它要求compareAndSet同时校验值和stamp,使“变过又回来”可被检测,避免CAS误判;需每次get(int[])获取最新stamp并递增使用,否则无效。

ABA 问题不是值没变,而是“变过又回来”却没人知道——这会让 CAS 误判为安全,实际已破坏业务一致性。AtomicStampedReference 不是掩盖 ABA,而是让每次修改都留下可验证的痕迹。
ABA 怎么悄悄搞坏你的逻辑?
想象一个共享账户余额,初始为 100 元:
- 线程 A 读到余额 = 100,准备执行「+50」操作,但刚读完就被挂起
- 线程 B 快速执行:100 → 0(提现)→ 100(退款到账),版本未记录,值“复原”
- 线程 A 恢复,CAS 判断“还是 100”,于是把余额设为 150
- 结果:用户实际只有一笔退款,却被多加了 50 元——钱凭空多出来
这不是理论漏洞,而是真实发生在资金、库存、节点复用等场景中的逻辑错乱。关键点在于:CAS 只认“当前值是否匹配”,不关心“中间有没有被动过”。
AtomicStampedReference 如何打上可信“时间戳”
它把引用和一个整数 stamp 绑定为原子对,每次更新必须同时满足“值对 + 版本对”,否则拒绝。核心不在防改回,而在“改回也得留痕”:
- 初始化:
new AtomicStampedReference(100, 0) - 读取必须用
get(int[] stampHolder),一次拿到值和当前 stamp - CAS 调用形如:
compareAndSet(100, 150, oldStamp, oldStamp + 1),新 stamp 必须基于刚读到的值计算 - 若线程 B 已完成 100→0→100,stamp 就从 0→1→2;线程 A 拿着 oldStamp=0 去 CAS,必然失败
写法不对,加了 stamp 也白搭
常见错误不是类不会用,而是逻辑没闭环:
- 在循环重试中,没重新调用
get()获取最新 stamp,导致反复用旧 stamp 尝试 - 手动写死
compareAndSet(..., 0, 1),忽略并发下多个线程可能同时读到 stamp=0 并都+1,造成 stamp 冲突 - 误把 stamp 当业务版本号(比如认为 stamp=3 代表“第三版订单”),其实它只是递增计数器,无业务语义
- 忘记 stamp 是 int 类型,虽极少溢出,但若长期运行且高频更新,需留意回绕到负数
它适合什么,不适合什么
AtomicStampedReference 解决的是「单次 CAS 需感知中间修改」的场景,不是万能锁替代品:
- ✅ 适合:无锁栈/队列节点复用检测、状态机跃迁校验(如 INIT→PROCESSING→INIT)、资源是否被回收再分配
- ❌ 不适合:需要跨服务传递 stamp 的分布式场景(stamp 是本地内存状态)、含多步判断的复合操作(如“查余额>100才扣款”,仍需锁或事务)
- ⚠️ 注意:比 AtomicReference 多一次字段读写,性能低约 5%–15%,但换来的是逻辑正确性保障










