CAS~~

CAS

什么是CAS
CAS其实就是Compare And Swap的简写,乐观锁的代表,它的功能比较当前工作内存中的值和主内存中的值,如果相同则执行指定值的更改(交换),否则继续比较直到主内存和工作内存中的值一致为止。它整个过程是原子的,因为它是一条CPU并发原语。原语的执行必须是连续的,在执行过程中不允许中断,也就是说CAS是一条原子指令,不会造成所谓的数据不—致的问题。CAS在Java中的体现就是Unsafe(魔术类)中的各个方法。

CAS的缺点
① 如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,这种自旋比较会给CPU带来很大的开销。
② 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,
但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
ABA问题:内存值A被其他线程多次修改,最终又改回了预期值A,但是这种修改再上述规则发现不了

AtomicInteger类底层很多方法都是用CAS,我们以getAndIncrement这个方法为例:
在这里插入图片描述
var1Atomiclnteger对象本身。
var2 该对象值得引用地址。
var4 需要变动的数量。
var5 使用var1 var2找出的主内存中真实的值。
用该对象当前的值与var5比较;
如果相同,更新 var5+var4 并且返回true
如果不同,继续取值然后再比较,直到更新完成。

过程详解

假设线程A和线程B同时执行getAndInt操作(分别跑在不同的CPU上)
1、AtomicInteger里面的value原始值为3,即主内存中Atomiclntegervalue3,根据JMM模型,线程A线程B各自持有一份值为3的副本,分别存储在各自的工作内存

2、线程A通过getIntVolatile(var1 , var2)拿到value3,这是线程A被挂起(该线程失去CPU执行权)

3、线程B也通过getIntVolatile(var1, var2)方法获取到value值也是3,此时刚好线程B没有被挂起,并执行了compareAndSwapilnt方法,比较内存的值也是3,成功修改内存值为4,线程B打完收工。

4、这是线程A恢复,执行CAS方法,比较发现自己手里的数字3和主内存中的数字4不一致,说明该值已经被其它线程抢先一步修改过了,那么线程A本次修改失败,只能够重新去主内存里读取value值后在来一遍了,也就是在执行do while

5、线程A重新获取value值,因为变量valuevolatile修饰,所以其它线程对它的修改,线程A总能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。
在这里插入图片描述
讲一讲ActomicInteger为什么要用CAS而不用Synchronized?

这里没有用synchronized,而用CAS,这样提高了并发性,也能够实现一致性,是因为每个线程进来后,进入的do while循环,然后不断的获取内存中的值,判断是否为最新,然后在进行更新操作。

AtomicReference

原子引用:AtomicReference(奥套米克瑞佛恩斯)是作用是对 ”对象” 进行原子操作。 提供了一种读和写都是原子性的对象引用变量。原子意味着多个线程试图改变同一个AtomicReference(例如比较和交换操作)将不会使得AtomicReference处于不一致的状态。

/**
 * @author acoffee
 * @create 2022-01-22 17:25
 */
public class AutomicReferenceDemo {
    public static void main(String[] args) {
        String str1 = "str1";
        String str2 = "str2";

        AtomicReference<Object> atomicReference = new AtomicReference<>();
        atomicReference.set(str1);

        System.out.println(atomicReference.compareAndSet(str1,str2)+": "+atomicReference.get().toString());//true: str2
        System.out.println(atomicReference.compareAndSet(str1,str2)+": "+atomicReference.get().toString());//false: str2
    }
}

ABA问题

引出ABA问题

public class ABADemo {
    static AtomicReference atomicReference = new AtomicReference(100);


    public static void main(String[] args) {
        System.out.println("=========展示ABA问题==========");
        new Thread(() -> {
            //完成ABA操作
            atomicReference.compareAndSet(100, 101);
            atomicReference.compareAndSet(101, 100);
        },"t1").start();

        new Thread(() ->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicReference.compareAndSet(100,2019)+"\t"+atomicReference.get());// true 2019
        },"t2").start();

    }
}

在这里插入图片描述

ABA问题的解决
可以给它增加一个版本号或者时间戳,JDK给我们提供了AtomicStampedReference 这个类,他就相当于给数据增加的一个标记,就算数据修改过又修改回来,它的标记会变,所以也就不会CAS的条件,会继续从主内存中拿值尝试修改

public class ABASolveDemo {
    static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);

    public static void main(String[] args) {
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t第一次版本号: "+stamp);
            //暂停一秒t1线程
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //造成ABA问题
            atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t第二次版本号: "+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t第三次版本号: "+atomicStampedReference.getStamp());

        },"t1").start();

        new Thread(() ->{
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t第一次版本号: "+stamp);
            //暂停三秒t2线程,保证上面完成了一次ABA操作
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean flag = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"是否修改成功: "+ flag+"\n当前最新实际版本号: "+atomicStampedReference.getStamp()
            +"\n当前实际最新值: "+atomicStampedReference.getReference());
        },"t2").start();

    }
}

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值