什么叫做CAS

原文链接:什么叫做CAS – 编程屋

1 什么叫做CAS?

CAS(compare and swap)比较并交换,在平时开发中其实很多底层都是用cas来实现的,像原子类的底层原理就是cas,乐观锁的底层原理也是cas。原子类的用法可见下面这篇博客

volatile ~原子类 – 编程屋

cas的特点:当多个线程同时使用cas去更新一个变量的时候,只有其中一个线程能够操作成功,其他的线程都能够操作失败,但是更新失败的线程不会阻塞,但失败的线程会自旋尝试更新(默认都有尝试更新的次数)。

CAS中的核心操作:内存值V、预期值A、要修改的值B。当且仅当预期值A和当前的内存值V相等时才会将内存值修改为B。

2 CAS带来了什么问题?

CAS带来的ABA问题

比如线程1已经将利用CAS修改变量值A,但是在修改之前其他线程已经将A变成了B,然后又变成了A,即A->B->A问题。因为线程1在操作的时候发现值依然为A,所以根据CAS的机制会操作成功,但是其实这个值已经被其他线程修改过了,只是线程1不知道而已,这就导致了ABA问题。

demo实例:

    private static AtomicReference<Integer> atomicInteger = new AtomicReference(100);


    public static void main(String[] args) throws InterruptedException {
        System.out.println("=================以下是ABA问题产生==========================");
     new Thread(() -> {
            atomicInteger.compareAndSet(100, 101);
            atomicInteger.compareAndSet(101, 100);
        },"t1").start();


        new Thread(() -> {
            try {
                //暂停1s中t2线程,保证了上面的t1线程完成了一次ABA操作(当main
                // 线程启动之后,t1线程和t2线程不知道谁先执行,所以让t2线程先睡1秒,这样即使t2线程先执行的情况下。睡1s时,也会被t1线程抢到完成一次ABA的操作)
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean result = atomicInteger.compareAndSet(100, 2022);
            System.out.println("是否修改成功:" + result +"\t"+"修改之后的值为"+atomicInteger.get());
        },"t2").start();



    }

由demo可以看到两个线程,第一个线程将100改为101后,又将101改为了100,线程2依然可以将100改为其他的值(2022)。所以说CAS的确带来了ABA问题。

 3 如何解决ABA问题?

在java中,AtomicStampedReference通过包装[E,Integer]的元组来对象对象标记版本戳stamp,从而避免ABA问题。

demo示例:

private static AtomicReference<Integer> atomicInteger = new AtomicReference(100);
    private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100,1);

    public static void main(String[] args) throws InterruptedException {
        System.out.println("=================以下是ABA问题产生==========================");
     new Thread(() -> {
            atomicInteger.compareAndSet(100, 101);
            atomicInteger.compareAndSet(101, 100);
        },"t1").start();


        new Thread(() -> {
            try {
                //暂停1s中t2线程,保证了上面的t1线程完成了一次ABA操作(当main
                // 线程启动之后,t1线程和t2线程不知道谁先执行,所以让t2线程先睡1秒,这样即使t2线程先执行的情况下。睡1s时,也会被t1线程抢到完成一次ABA的操作)
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean result = atomicInteger.compareAndSet(100, 101);
            System.out.println("是否修改成功:" + result +"\t"+"修改之后的值为"+atomicInteger.get());
        },"t2").start();

        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) {}

        System.out.println("=================以下是ABA问题的解决==========================");

        new Thread(() -> {
           //拿到此时版本号
           int stamp = atomicStampedReference.getStamp();
           System.out.println(Thread.currentThread().getName()+"\t第1次版本号为"+stamp);

           //暂停1s钟t3线程,让cpu去调度t4线程拿到同一个初始版本号
           try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {}
            //将100设置成101并增加版本号
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName()+"\t第2次版本号为"+atomicStampedReference.getStamp());
            //将101设置成100并增加版本号
            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1);

            System.out.println(Thread.currentThread().getName()+"\t第3次版本号为"+atomicStampedReference.getStamp());

        },"t3线程").start();

       new Thread(() -> {
            //拿到此时版本号
           int stamp = atomicStampedReference.getStamp();
           System.out.println(Thread.currentThread().getName()+"\t第1次版本号为"+stamp);
           //暂停t4线程3秒钟,保证t3线程完成一次ABA操作
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { }
            //将100设置成101并增加版本号
            boolean refResult = atomicStampedReference.compareAndSet(100, 2021, stamp, stamp + 1);

           System.out.println("是否修改成功:" + refResult +"\t"+"实际最新值为"+atomicStampedReference.getReference()+"\t"+"当前实际版本号为:"+atomicStampedReference.getStamp());

        },"t4线程").start();

控制台输出:

  可以看出,加入了版本号控制,解决了ABA的问题

4 CAS有什么缺点?

1)ABA问题

2)循环时间长,开销较大

以上只是部分内容,为了维护方便,本文已迁移到新地址:什么叫做CAS – 编程屋

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值