cas无法识别中间篡改,因其仅比对当前值与预期值是否相等,不记录修改历史;aba问题由此产生,需通过atomicstampedreference等带版本号的方案解决。

CAS 本身无法识别“中间被篡改过”,它只比对当前值是否等于预期值,不关心这个值是否曾被改过又改回来。也就是说,只要最终值没变,CAS 就认为没人动过——这正是 ABA 问题的根源。
为什么 CAS 看不出中间状态变化
CAS 的设计是轻量级的乐观判断:它只检查“现在是不是还是我读到的那个数”。比如线程 A 读到值为 100,稍后执行 CAS 想改成 150;此时线程 B 快速执行了 100 → 200 → 100,A 恢复后发现还是 100,就直接提交成功。但中间的 200 状态(比如某个资源被临时释放又重建)已经丢失,业务逻辑可能出错。
根本原因在于:
- CAS 指令本身只接受三个参数:内存地址、旧预期值、新值,没有版本号或时间戳字段
- JVM 层面的
compareAndSwapInt等方法也只做数值比对,不记录修改历史 - 它本质是硬件级原子指令(如 x86 的
cmpxchg),专注快,不负责状态追踪
识别篡改的关键:引入额外状态标识
要让 CAS “感知中间变化”,必须在数值之外附加可区分的状态标记。Java 提供了现成方案:
- AtomicStampedReference:把整型“版本号”和引用打包在一起,每次修改都递增 stamp。即使引用值回到原样,stamp 已不同,CAS 就会失败
- AtomicMarkableReference:用一个布尔标记代替版本号,适合只需区分“是否被改过”的简单场景
- 手动加版本字段:在自定义对象中增加
version或timestamp字段,用AtomicReference<mydata></mydata>包裹整个对象,CAS 时连同版本一起比对
实际编码中怎么用 AtomicStampedReference
以防止链表节点被误重用为例:
- 初始化:
AtomicStampedReference<node> ref = new AtomicStampedReference(head, 0);</node> - 读取时同时拿到值和 stamp:
int[] stampHolder = new int[1]; Node current = ref.get(stampHolder); int stamp = stampHolder[0]; - 更新时必须提供旧值 + 旧 stamp:
ref.compareAndSet(current, newNode, stamp, stamp + 1); - 只要期间有其他线程调过
compareAndSet,stamp 就已加 1,当前 CAS 就会因 stamp 不匹配而失败
不是所有场景都需要防 ABA
ABA 是否构成真实风险,取决于业务语义:
- 计数器累加(
AtomicInteger.getAndIncrement):值来回变不影响结果,无需处理 - 无锁栈/队列的 head/tail 指针:节点被弹出又压回同一位置,可能引发内存错误或无限循环,必须防护
- 数据库乐观锁(带 version 字段的 update):本质上就是应用层的 stamped CAS,已默认规避 ABA
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











