1、有两个线程同时去修改一个变量的值,比如线程1、线程2,都更新变量值,将变量值从 A 更新到 B 。
2、首先线程1 获取到 CPu 的时间片, 线程2 由于某些原因发生阻塞进行等待,此时线程1 进行比较更新(CompareAndSwap),成功将变量的值从 A 更新成 B。
3、更新完毕之后,恰好又有线程3 进来想要把变量的值从 B 更新成 A,线程3 进行比较更新,成功将变量的值从 B 更新成 A。
4、 线程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带来非常大的执行开销