浅谈CAS的ABA问题及解决办法

553 阅读2分钟

最近有小伙伴问到我CAS存在的ABA问题,所以写个博客记录一下,话不多说直接上代码:

存在的ABA问题:

/*
    创建一个AtomicReference 用来实现CAS的ABA问题,默认值为100
 */
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);

我们使用JUC原子类方法AtomicReference(V initialvalue)来演示CAS存在的ABA问题:

public static void atomicReferenceStudent() {
    /*
        线程T1执行
        atomicReference.compareAndSet(期望值, 更新值);
        若atomicReference的值与期望值匹配,则进行更新。
     */
    new Thread(() -> {
        atomicReference.compareAndSet(100, 101);
        atomicReference.compareAndSet(101, 100);
    }, "T1").start();

    /*
        线程T2执行
     */
    new Thread(() -> {
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        /*
            当前执行是否成功:true,当前值为:2022
            打印表示出T2执行成功,说明产生了ABA问题,因为内存值已经被线程T1修改了,说明了CAS的原子引用操作是只看重头和尾不关心过程的
         */
        System.out.println("当前执行是否成功:" + atomicReference.compareAndSet(100, 2022) + ",当前值为:" + atomicReference.get());
    }, "T2").start();
}

调用main方法查看打印结果:

//启动类
public static void main(String[] args) {
    atomicReferenceStudent();
}

打印结果: 当前执行是否成功:true,当前值为:2022

如何解决ABA问题:

/*
    创建一个AtomicStampedReference代替AtomicReference 用来解决CAS的ABA问题,默认值为100,版本号默认值为1
 */
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

我们使用JUC原子类AtomicStampedReference(V initialRef, int initialStamp)用来解决CAS的ABA问题:

public static void atomicStampedReferenceStudent() {

    //获取版本号,默认版本号为1
    int stamp = atomicStampedReference.getStamp();

    new Thread(() -> {
        //线程拿到版本号为1的内存值100,符合期望值,将值更新为101,版本号+1,本次操作完成后版本号为2的内存值101
        atomicStampedReference.compareAndSet(100, 101, stamp, atomicStampedReference.getStamp() + 1);
        //线程拿到版本号为2的内存值101,符合期望值,将值更新为101,版本号+1,本次操作完成后版本号为3的内存值100
        atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
    }, "T1").start();

    try {
        Thread.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    new Thread(() ->
            //线程拿到版本号为1的内存值100,符合期望值,但版本号不一致,内存值已被操作,则本次操作失败
            System.out.println("当前执行是否成功:" + atomicStampedReference.compareAndSet(100, 2022, stamp, atomicStampedReference.getStamp() + 1) +
                    ",当前值为:" + atomicStampedReference.getReference() +
                    ",当前版本号为:" + atomicStampedReference.getStamp()), "T2").start();
}

调用main方法打印结果:

//启动类
public static void main(String[] args) {
    atomicStampedReferenceStudent();
}

打印结果: 当前执行是否成功:false,当前值为:100,当前版本号为:3

好了,目前就介绍到这里,谢谢您的观看,若有问题欢迎留言一起讨论~