AtomicStampedReference 不自动解决ABA问题,而是通过引用+版本号原子二元组使状态变化可识别;必须成对调用get(int[] stampHolder)获取严格对应的引用和版本号,再用四参数compareAndSet校验同一时刻状态。
AtomicStampedReference 不是“自动解决” ABA 问题的黑盒,而是把检测权交还给你——它通过引用+版本号的原子二元组,让“值没变但状态已变”变得可识别。关键不在引入版本号,而在你是否用对了它。
为什么只比值不行?ABA 的本质是状态丢失线程看到值还是 A,就以为中间没发生任何事。但现实中,A 可能已被回收、复用、标记为无效、甚至指向完全不同的内存地址。比如无锁栈中节点被弹出又压回,表面引用相同,实际逻辑上下文已断裂。版本号不是为了计数,而是给每次修改打上不可磨灭的时间戳。
必须成对读取:get(int[] stampHolder) 是唯一安全入口分开调用 getReference() 和 getStamp() 会拿到不同时间点的快照,CAS 必然失败或误成功。正确做法只有一种:声明int[] stamp = new int[1]调用String current = ref.get(stamp),此时 stamp[0] 与 current 严格对应后续 compareAndSet(current, next, stamp[0], stamp[0] + 1) 才真正校验同一时刻的状态compareAndSet 四参数不是负担,是强制契约expectedRef、newRef、expectedStamp、newStamp 缺一不可。JDK 故意不提供两参数重载,就是防止你绕过版本校验。常见错误包括:硬编码 newStamp = 1,第二次调用直接失效循环中复用第一次读到的 stamp[0],忽略中间可能已跳变多次用 equals() 比较 expectedRef,而 AtomicStampedReference 内部用 == 判等版本号管理要贴合业务语义,而非机械自增oldStamp + 1 适合多数场景,但需注意:stamp 是 int,溢出后变为 Integer.MIN_VALUE,若服务生命周期极长且更新极频繁,需确认 wrap-around 是否可接受一次业务操作若跨越多个状态(如 INIT → PROCESSING → DONE),建议按语义分配固定 stamp 值(0/1/2),而非统一递增Android 平台 API 24 以下不支持该类,反射调用会抛 NoSuchMethodError
