由于CAS原子操作性能高,因此其在JUC包中被广泛引用,只不过如果使用得不合理,CAS原子操作就会存在ABA问题。
1、了解ABA问题
什么是ABA问题?比如一个线程A从内存位置M中取出V1,另一个线程B也取出V1。现在假设线程B进行了一些操作之后将M位置的数据V1变成了V2,然后又在一些操作之后将V2变成了V1。之后,线程A进行CAS操作,但是线程A发现M位置的数据仍然是V1,然后线程A操作成功。尽管线程A的CAS操作成功,但是不代表这个过程是没有问题的,线程A操作的数据V1可能已经不是之前的V1,而是被线程B替换过的V1,这就是ABA问题。
2、ABA问题解决方案
很多乐观锁的实现版本都是使用版本号(Version)方式来解决ABA问题。乐观锁每次在执行数据的修改操作时都会带上一个版本号,版本号和数据的版本号一致就可以执行修改操作并对版本号执行加1操作,否则执行失败。因为每次操作的版本号都会随之增加,所以不会出现ABA问题,因为版本号只会增加,不会减少。
3、使用AtomicStampedReference解决ABA问题
参考乐观锁的版本号,JDK提供了一个AtomicStampedReference类来解决ABA问题。AtomicStampReference在CAS的基础上增加了一个Stamp(印戳或标记),使用这个印戳可以用来觉察数据是否发生变化,给数据带上了一种实效性的检验。
AtomicStampReference的compareAndSet()方法首先检查当前的对象引用值是否等于预期引用,并且当前印戳(Stamp)标志是否等于预期标志,如果全部相等,就以原子方式将引用值和印戳(Stamp)标志的值更新为给定的更新值。
AtomicStampReference的构造器有两个参数,具体如下:
AtomicStampReference常用的几个方法如下:
AtomicStampedReference的CAS操作的定义如下:
compareAndSet()方法的第一个参数是原来的CAS中的参数,第二个参数是替换后的新参数,第三个参数是原来CAS数据旧的版本号,第四个参数表示替换后的新参数版本号。
进行CAS操作时,若当前引用值等于预期引用值,并且当前印戳值等于预期印戳值,则以原子方式将引用值和印戳值更新为给定的更新值。
4、使用AtomicMarkableReference解决ABA问题
AtomicMarkableReference是AtomicStampedReference的简化版,不关心修改过几次,只关心是否修改过。因此,其标记属性mark是boolean类型,而不是数字类型,标记属性mark仅记录值是否修改过。
AtomicMarkableReference适用于只要知道对象是否被修改过,而不适用于对象被反复修改的场景。