CAS的ABA问题及解决代码示例

1、ABA问题示例

package com.example.sgg.juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * ABA问题
 * Created by 奔跑的蜗牛 on 2022/5/5 0005.
 * 每天学习一点点,每天进步一点点
 */
public class ABADemo {

    // 主内存共享变量,初始值为1,版本号为1
    private static AtomicInteger atomicInteger = new AtomicInteger(1);

    public static void main(String[] args) {
        // t1,期望将1改为10
        new Thread(() -> {
            // 第一次拿到的时间戳
            int value = atomicInteger.get();
            System.out.println(Thread.currentThread().getName() + " 第1次拿到值为:" + value);
            // 休眠5s,确保t2执行完ABA操作
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // t2将时间戳改为了3,cas失败
            boolean b = atomicInteger.compareAndSet(value, 10);
            System.out.println(Thread.currentThread().getName() + " CAS是否成功:" + b);
            System.out.println(Thread.currentThread().getName() + " 当前最新值为:" + atomicInteger.get());
        }, "t1").start();

        // t2进行ABA操作
        new Thread(() -> {
            // 第一次拿到的时间戳
            int value = atomicInteger.get();
            System.out.println(Thread.currentThread().getName() + " 第1次拿到值为:" + value);
            // 休眠,修改前确保t1也拿到同样的副本,初始值为1
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 将副本改为20,再写入,紧接着又改为1,写入,每次提升一个时间戳,中间t1没介入
            atomicInteger.compareAndSet(value, 20);
            System.out.println(Thread.currentThread().getName() + " 第2次拿到值为:" + atomicInteger.get());
            atomicInteger.compareAndSet(20, value);
            System.out.println(Thread.currentThread().getName() + " 第3次拿到值为:" + atomicInteger.get());

        }, "t2").start();
    }
}

2、ABA问题解决示例

package com.example.sgg.juc;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * 解决ABA问题
 * Created by 奔跑的蜗牛 on 2022/5/5 0005.
 * 每天学习一点点,每天进步一点点
 */
public class ABASolveDemo {

    // 主内存共享变量,初始值为1,版本号为1
    private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(1, 1);

    public static void main(String[] args) {
        // t1,期望将1改为10
        new Thread(() -> {
            // 第一次拿到的时间戳
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + " 第1次时间戳:" + stamp + " 值为:" + atomicStampedReference.getReference());
            // 休眠5s,确保t2执行完ABA操作
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // t2将时间戳改为了3,cas失败
            boolean b = atomicStampedReference.compareAndSet(1, 10, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName() + " CAS是否成功:" + b);
            System.out.println(Thread.currentThread().getName() + " 当前最新时间戳:" + atomicStampedReference.getStamp() + " 最新值为:" + atomicStampedReference.getReference());
        }, "t1").start();

        // t2进行ABA操作
        new Thread(() -> {
            // 第一次拿到的时间戳
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + " 第1次时间戳:" + stamp + " 值为:" + atomicStampedReference.getReference());
            // 休眠,修改前确保t1也拿到同样的副本,初始值为1
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 将副本改为20,再写入,紧接着又改为1,写入,每次提升一个时间戳,中间t1没介入
            atomicStampedReference.compareAndSet(1, 20, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName() + " 第2次时间戳:" + atomicStampedReference.getStamp() + " 值为:" + atomicStampedReference.getReference());
            atomicStampedReference.compareAndSet(20, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + " 第3次时间戳:" + atomicStampedReference.getStamp() + " 值为:" + atomicStampedReference.getReference());

        }, "t2").start();
    }
}

简单理解就是AtomicStampedReference使用了单向版本号,不可逆转,每次修改则版本号递增,以此来解决ABA的问题

3、ABA问题带来的危害

个人理解:

1、一条线程造成的ABA问题对业务的数据不会造成影响,因为最终一致,可以看做这个ABA为回滚动作;

举例说明:

一个线程同时做了,购票(票数减一)和退票操作(票数加一),就算是发生了ABA,而另一个购票线程依然还是根据旧的票数减一操作,最终结果不受影响(此处为举例,实际购票退票理论上不是一个线程做的

2、对于两条以上线程造成的ABA问题对业务数据会造成影响,因为影响的最终一致性;

举例说明:

小牛取款,由于机器不太好使,多点了几次取款操作。后台threadA和threadB工作,
此时threadA操作成功(100->50),threadB阻塞。正好牛妈打款50元给小牛(50->100),
threadC执行成功,之后threadB运行了,又改为(100->50), 钱变少了,因为原本多点击的threadB是无法执行的,因此牛妈打款后,最终金额应该是100。

你可能感兴趣的:(Java,多线程,java,开发语言)