# ABA 问题遇到过吗,详细说一下
- 有两个线程同时去修改一个变量的值,比如线程 1、线程 2,都更新变量值,将变量值从 A 更新成 B。
- 首先线程 1 获取到 CPU 的时间片,线程 2 由于某些原因发生阻塞进行等待,此时线程 1 进行比较更新(CompareAndSwap),成功将变量的值从 A 更新成 B。
- 更新完毕之后,恰好又有线程 3 进来想要把变量的值从 B 更新成 A,线程 3 进行比较更新,成功将变量的值从 B 更新成 A。
- 线程 2 获取到 CPU 的时间片,然后进行比较更新,发现值是预期的 A,然后有更新成了 B。但是线程 1 并不知道,该值已经有了 A->B->A 这个过程,这也就是我们常说的 ABA 问题。
举个具体的例子:
小明在提款机,提取了 50 元,因为提款机问题,
有两个线程,同时把余额从 100 变为 50
线程 1(提款机):获取当前值 100,期望更新为 50,
线程 2(提款机):获取当前值 100,期望更新为 50,
线程 1 成功执行,线程 2 某种原因 block 了,这时,某人给小明汇款 50
线程 3(默认):获取当前值 50,期望更新为 100,
这时候线程 3 成功执行,余额变为 100,
线程 2 从 Block 中恢复,获取到的也是 100,compare 之后,继续更新余额为 50!!!
此时可以看到,实际余额应该为 100(100-50+50),但是实际上变为了 50(100-50+50-50)
# 如何避免 ABA 问题:
可以通过加版本号或者加时间戳解决,或者保证单向递增或者递减就不会存在此类问题。
atomic 包下的 AtomicStampedReference 类:其 compareAndSet 方法首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用的该标志的值设置为给定的更新值。
# CAS 导致的其他问题
1、只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量 i=2,j=a,合并一下 ij=2a,然后用 CAS 来操作 ij。从 Java1.5 开始 JDK 提供了 AtomicReference 类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行 CAS 操作。
2、循环时间长开销大
自旋 CAS 如果长时间不成功,会给 CPU 带来非常大的执行开销。