【JAVA并发】CAS(第二弹)

发布于:2023-01-11 ⋅ 阅读:(503) ⋅ 点赞:(0)

前言:更多 CAS 相关文章:【JAVA并发】CAS(第一弹)

 一、CAS存在的问题:

1、ABA问题

上文说到,CAS在更新值的时候,会判断要修改的A 是否和预期值相同,如果相同则更新新值。但会存在一种情况:线程 t2 在 t1 写回前,已经读取 A,修改成了 B,又修改成了 A,再写回时,满足要修改的值和预期值相同,更新成功。整个过程 t1 是无法知道的, t1 在更新时也能成功;而之后 t2 如果还想用 A,就读取不到了,因为已经是 t1 修改后的 B,这就造成了数据不一致的问题。

产生ABA问题的代码示例:

public class ABAExample {

    public static AtomicInteger atomicInteger = new AtomicInteger(100);

    public static void main(String[] args) {
        Thread t2 = new Thread(() -> {
             atomicInteger.compareAndSet(100,101);
             System.out.println(Thread.currentThread().getName() + "当前的值为:" + atomicInteger.get());
             atomicInteger.compareAndSet(101,100);
             System.out.println(Thread.currentThread().getName() + "当前的值为:" + atomicInteger.get());
        t1.start();

        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicInteger.compareAndSet(100, 200);
            System.out.println(Thread.currentThread().getName() + "当前的值为:" + atomicInteger.get());
        });
        t2.start();
    }
}

日志打印:
Thread-0当前的值为:101
Thread-0当前的值为:100
Thread-1当前的值为:200

目前在 JDK 的 atomic 包里提供了一个类 AtomicStampedReference 来解决ABA问题。这个类的compareAndSet 方法作用是首先检查当前引用是否等于预期引用,并且当前版本号是否等于预期版本号,如果全部相等,则以原子方式将该引用和该版本的值设置为给定的更新值。

解决方法实例:

public class ABASolve {
    //引入AtomicStampedReference来解决ABA问题
    public static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);

    public static void main(String[] args) {
        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

       atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "当前的值为:" + atomicStampedReference.getReference() + ",当前的版本号:" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);
             System.out.println(Thread.currentThread().getName() + "当前的值为:" + atomicStampedReference.getReference() + ",当前的版本号:" + atomicStampedReference.getStamp());

        });
        t2.start();

        Thread t1 = new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            atomicStampedReference.compareAndSet(100,200,stamp,stamp + 1);
            System.out.println(Thread.currentThread().getName() + "当前的值为:" + atomicStampedReference.getReference()  + ",当前的版本号:" + atomicStampedReference.getStamp());
        });
        t1.start();
    }
}

日志打印:
Thread-0当前的值为:101,当前的版本号:2
Thread-0当前的值为:100,当前的版本号:3
Thread-1当前的值为:100,当前的版本号:3

可以看到线程 t1 最后是更新失败的,虽然要修改的值与预期值相同,但版本号不同。

2、CPU开销:

当线程判断要修改的值和预期值不同时,会循环比较,直到相等为止,造成CPU开销。

3、只能保证一个共享变量的原子操作:

对于多个共享变量可以使用可重入锁,或者封装到一个对象内,使用 AtomicReference 类来保证引用对象之间的原子性。

 

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到