精华内容
下载资源
问答
  • 什么是CAS

    千次阅读 2020-03-26 00:07:11
    一、什么是CAS CAS: 全称Compare and swap,字面意思:”比较并交换“,是一种轻量级锁。 线程在读取数据是不进行加锁,在准备修改数据时,先去查询原值,操作的时候比较原值是否被修改,若未被其他线程修改则写入...

    一、什么是CAS

    CAS: 全称Compare and swap,字面意思:”比较并交换“,是一种轻量级锁。
    线程在读取数据是不进行加锁,在准备修改数据时,先去查询原值,操作的时候比较原值是否被修改,若未被其他线程修改则写入数据,若已经被修改,就要重新执行读取流程。
    举个例子:
    我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。 1. 比较 A 与 V 是否相等。(比较) 2. 如果比较相等,将 B 写入 V。(交换) 3. 返回操作是否成功。
    当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。可见 CAS 其实是一个乐观锁。

    二、关于ABA问题

    1.什么是ABA问题
    1.线程1读取了数据A,线程2也读取了数据A。
    2.线程2通过CAS比较,发现是原数据A没错,于是就将数据A改为了数据B。
    3.线程3此时通过CAS比较,发现原数据就是数据B,于是就将数据B改成数据A。
    4.此时,线程1通过CAS比较,发现原数据是A,就改成了自己要改的值。
    虽然说线程1最后能能操作成功,但是这样已经违背了CAS的初衷,数据已经被修改过了,按CAS的原则来讲,CAS是不应该修改成功的。

    2.如何解规避ABA问题

    可以设置一个自增的标志位,数据的每一次修改标志位都会自增,比较标志位的值,还可以加上时间戳,显示上一次修改的时间,比较时间戳的值。

    三、CAS的缺点

    1.循环时间长,开销大,如果CAS失败,会一直尝试,如果CAS一直不成功,会给CPU带来很大的开销
    2.只能保证一个共享变量的原子操作
    3.就是上面说到的ABA问题

    展开全文
  • 什么是CAS机制?

    万次阅读 多人点赞 2018-03-12 18:28:16
    我们现在来说什么是ABA问题。假设内存中有一个值为A的变量,存储在地址V中。 此时有三个线程想使用CAS的方式更新这个变量的值,每个线程的执行时间有略微偏差。线程1和线程2已经获取当前值,线程3还未获取当前...

    我们先看一段代码:

    启动两个线程,每个线程中让静态变量count循环累加100次。

    最终输出的count结果一定是200吗?因为这段代码是非线程安全的,所以最终的自增结果很可能会小于200。我们再加上synchronized同步锁,再来看一下。

    加了同步锁之后,count自增的操作变成了原子性操作,所以最终输出一定是count=200,代码实现了线程安全。虽然synchronized确保了线程安全,但是在某些情况下,这并不是一个最有的选择。

    关键在于性能问题。

    synchronized关键字会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式和内核模式的转换,代价比较高。

    尽管JAVA 1.6为synchronized做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过过度,但是在最终转变为重量级锁之后,性能仍然比较低。所以面对这种情况,我们就可以使用java中的“原子操作类”。

    所谓原子操作类,指的是java.util.concurrent.atomic包下,一系列以Atomic开头的包装类。如AtomicBoolean,AtomicUInteger,AtomicLong。它们分别用于Boolean,Integer,Long类型的原子性操作。

    现在我们尝试使用AtomicInteger类:

    使用AtomicInteger之后,最终的输出结果同样可以保证是200。并且在某些情况下,代码的性能会比synchronized更好。

    而Atomic操作类的底层正是用到了“CAS机制”。

    CAS是英文单词Compare and Swap的缩写,翻译过来就是比较并替换。

    CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。

    更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

    我们看一个例子:

    1. 在内存地址V当中,存储着值为10的变量。

    2. 此时线程1想把变量的值增加1.对线程1来说,旧的预期值A=10,要修改的新值B=11.

    3. 在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。

    4. 线程1开始提交更新,首先进行A和地址V的实际值比较,发现A不等于V的实际值,提交失败。

    5. 线程1 重新获取内存地址V的当前值,并重新计算想要修改的值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋。

    6. 这一次比较幸运,没有其他线程改变地址V的值。线程1进行比较,发现A和地址V的实际值是相等的。

    7. 线程1进行交换,把地址V的值替换为B,也就是12.

    从思想上来说,synchronized属于悲观锁,悲观的认为程序中的并发情况严重,所以严防死守,CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去重试更新。

    在java中除了上面提到的Atomic系列类,以及Lock系列类夺得底层实现,甚至在JAVA1.6以上版本,synchronized转变为重量级锁之前,也会采用CAS机制。

    CAS的缺点:

    1) CPU开销过大

    在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。

    2) 不能保证代码块的原子性

    CAS机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。

    3) ABA问题

    这是CAS机制最大的问题所在。(后面有介绍)

     

    我们下面来介绍一下两个问题:

    1. JAVA中CAS的底层实现

    2. CAS的ABA问题和解决办法。

    我们看一下AtomicInteger当中常用的自增方法incrementAndGet:

    public final int incrementAndGet() {

        for (;;) {

            int current = get();

            int next = current + 1;

            if (compareAndSet(current, next))

                return next;

        }

    }

    private volatile int value; 

    public final int get() {

        return value;

    }

    这段代码是一个无限循环,也就是CAS的自旋,循环体中做了三件事:

    1. 获取当前值

    2. 当前值+1,计算出目标值

    3. 进行CAS操作,如果成功则跳出循环,如果失败则重复上述步骤

    这里需要注意的重点是get方法,这个方法的作用是获取变量的当前值。

    如何保证获取的当前值是内存中的最新值?很简单,用volatile关键字来保证(保证线程间的可见性)。我们接下来看一下compareAndSet方法的实现:

    compareAndSet方法的实现很简单,只有一行代码。这里涉及到两个重要的对象,一个是unsafe,一个是valueOffset。

    什么是unsafe呢?Java语言不像C,C++那样可以直接访问底层操作系统,但是JVM为我们提供了一个后门,这个后门就是unsafe。unsafe为我们提供了硬件级别的原子操作。

    至于valueOffset对象,是通过unsafe.objectFiledOffset方法得到,所代表的是AtomicInteger对象value成员变量在内存中的偏移量。我们可以简单的把valueOffset理解为value变量的内存地址。

    我们上面说过,CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。

    而unsafe的compareAndSwapInt方法的参数包括了这三个基本元素:valueOffset参数代表了V,expect参数代表了A,update参数代表了B。

    正是unsafe的compareAndSwapInt方法保证了Compare和Swap操作之间的原子性操作。

    我们现在来说什么是ABA问题。假设内存中有一个值为A的变量,存储在地址V中。

    此时有三个线程想使用CAS的方式更新这个变量的值,每个线程的执行时间有略微偏差。线程1和线程2已经获取当前值,线程3还未获取当前值。

    接下来,线程1先一步执行成功,把当前值成功从A更新为B;同时线程2因为某种原因被阻塞住,没有做更新操作;线程3在线程1更新之后,获取了当前值B。

    在之后,线程2仍然处于阻塞状态,线程3继续执行,成功把当前值从B更新成了A。

    最后,线程2终于恢复了运行状态,由于阻塞之前已经获得了“当前值A”,并且经过compare检测,内存地址V中的实际值也是A,所以成功把变量值A更新成了B。

    看起来这个例子没啥问题,但如果结合实际,就可以发现它的问题所在。

    我们假设一个提款机的例子。假设有一个遵循CAS原理的提款机,小灰有100元存款,要用这个提款机来提款50元。

    由于提款机硬件出了点问题,小灰的提款操作被同时提交了两次,开启了两个线程,两个线程都是获取当前值100元,要更新成50元。

    理想情况下,应该一个线程更新成功,一个线程更新失败,小灰的存款值被扣一次。

    线程1首先执行成功,把余额从100改成50.线程2因为某种原因阻塞。这时,小灰的妈妈刚好给小灰汇款50元。

    线程2仍然是阻塞状态,线程3执行成功,把余额从50改成了100。

    线程2恢复运行,由于阻塞之前获得了“当前值”100,并且经过compare检测,此时存款实际值也是100,所以会成功把变量值100更新成50。

    原本线程2应当提交失败,小灰的正确余额应该保持100元,结果由于ABA问题提交成功了。

    怎么解决呢?加个版本号就可以了。

    真正要做到严谨的CAS机制,我们在compare阶段不仅要比较期望值A和地址V中的实际值,还要比较变量的版本号是否一致。

    我们仍然以刚才的例子来说明,假设地址V中存储着变量值A,当前版本号是01。线程1获取了当前值A和版本号01,想要更新为B,但是被阻塞了。

    这时候,内存地址V中变量发生了多次改变,版本号提升为03,但是变量值仍然是A。

    随后线程1恢复运行,进行compare操作。经过比较,线程1所获得的值和地址的实际值都是A,但是版本号不相等,所以这一次更新失败。

     

    在Java中,AtomicStampedReference类就实现了用版本号作比较额CAS机制。

     

    1. java语言CAS底层如何实现?

    利用unsafe提供的原子性操作方法。

    2.什么事ABA问题?怎么解决?

    当一个值从A变成B,又更新回A,普通CAS机制会误判通过检测。

    利用版本号比较可以有效解决ABA问题。

     

    展开全文
  • 目录 写在前面 一、初识CAS(比较并交换) ... 那么到底什么是CAS呢? 一、初识CAS(比较并交换) /** * CAS */ public class CASDemo { public static void main(String[] args) { Atom...

    目录

    写在前面

    一、初识CAS(比较并交换)

    二、CAS原理(自旋锁、unsafe类)

    三、CAS是什么

    四、CAS缺点

    五、ABA问题


    写在前面

        相信很多小伙伴对乐观锁、悲观锁都不陌生,但是说到java的cas,就蒙圈了。

        那么到底什么是CAS呢?

    一、初识CAS(比较并交换)

    /**
    * CAS
    */
    public class CASDemo {
    
        public static void main(String[] args) {
    
            AtomicInteger atomicInteger = new AtomicInteger(5);
            /* compareAndSet(int expect, int update)
                两个参数 期望值,修改值
                假如说主物理内存的值与expect的值相等,则修改成功,否则修改失败。
             */
            System.out.println(atomicInteger.compareAndSet(5, 2021) + "现在值是" + atomicInteger.get());
            /*
                因为主物理内存的值已经变成了2021,再由5修改为2022,会执行失败返回false
             */
            System.out.println(atomicInteger.compareAndSet(5, 2022) + "现在值是" + atomicInteger.get());
    
        }
    }

    二、CAS原理(自旋锁、unsafe类)

    1.之前解决i++不安全的问题,用到了一个这个方法:

    public final int getAndIncrement() {
        // this:当前对象。
        // valueOffset:内存偏移量(内存地址)
        // 1 加1
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

    2.源码:

    3.源码解析

    ①Unsafe

        是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。

        注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。

    ②变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。

    ③变量value用volatile修饰,保证了多线程之间的内存可见性。

    三、CAS是什么

    1.    CAS的全称为Conpare-And-Swap,它是一条CPU并发原语

        它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。

        CAS并发原语体现在Java语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓我的数据不一致问题

    2.源码解读:

    //一直循环,1.先得到当前数据,2.再进行比较并交换,3.如果交换成功就退出,交换失败就重新比较交换,直至成功。4.返回的是原值相当于i++

    3.var解释:

    var1:AtomicInteger对象本身。

    var2:该对象值的引用地址。

    var4:需要变动的数量。

    var5:是用var1 var2找出的主内存中真实的值。

    用该对象当前的值与var5比较:

    如果相同,更新var5+var4并且返回true,如果不同,继续取值然后比较,直到更新完成。    

    为什么用CAS而不用synchronized?

    synchronized加锁,每次只有一个线程能访问资源。并发性下降。

    CAS使用自旋锁,不需要手动加锁。

    4.CAS线程时冲突执行步骤:假设线程A和线程B两个线程同时执行getAndAddInt操作(分别跑在不同CPU上):

        1.AtomicInteger里面的value原始值为3,即主内存中AtomicInteger的value为3,根据JMM模型,线程A和线程B各自持有一份值为3的value的副本分别到各自的工作内存。

        2.线程A通过getIntVolatile(var1, var2)拿到value值3,这时线程A被挂起。

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

        4.此时线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的值3和主内存的值4不一致,说明该值已经被其他线程抢先一步修改过了,那A线程本次修改失败,只能重新读取重新来一遍了。

        5.线程A重新获取value值,因为变量value被volatile修饰,所以其它线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt进行比较交换,直到成功。

    5.

    6.CAS小总结

        CAS(CompareAndSwap)比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止。

        CAS应用:CAS有3个操作数,内存值V,旧的预期值A,要修改的更新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

    四、CAS缺点

    1.循环时间长,开销大

        我们可以看到getAndAddInt方法执行时,有个do-while。

        如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。

    2.只能保证一个共享变量的原子操作。

        当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。

    3.引出来ABA问题。

    五、ABA问题

    1.ABA问题是怎么产生的

        CAS会导致“ABA问题”。

        CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。

        比如说一个线程1从内存位置V中取出A,这时候另一个线程2也从内存中取出A,并且线程2进行了一些操作将值变成了B,然后线程2又将值变成A。此时线程1进行CAS操作发现内存中仍然是A,然后线程1操作成功。

        尽管线程1的CAS操作成功,但是不代表这个过程就是没有问题的。

        中间过程如果不介意别人动过,那无所谓。

        中间过程别人不能动,那就有问题了。

    2.解决ABA问题(加版本号)

    ①原子引用

    import java.util.concurrent.atomic.AtomicReference;
    /**
    * 原子引用
    * 如果想包装某个类,就用原子引用
    */
    public class AtomicReferenceDemo {
    
        public static void main(String[] args) {
            User u1 = new User("zhangsan", 14);
            User u2 = new User("lisi", 15);
            AtomicReference<User> atomicReference = new AtomicReference<>();
            atomicReference.set(u1);
            // true 设置为lisi
            System.out.println(atomicReference.compareAndSet(u1, u2) + "当前值" +atomicReference.get().getName());
            // false 还是lisi
            System.out.println(atomicReference.compareAndSet(u1, u2) + "当前值" +atomicReference.get().getName());
        }
    }
    
    class User{
        private String name;
        private Integer age;
    
        public User(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    }

    ②时间戳的原子引用    ABA问题的彻底解决

    import java.util.concurrent.atomic.AtomicReference;
    import java.util.concurrent.atomic.AtomicStampedReference;
    
    /**
    * ABA问题的解决
    */
    public class ABADemo {
    
        static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
        // 初始化值、版本号
        static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
        public static void main(String[] args) {
    
            // 1.以下是ABA问题的产生
            new Thread(() ->{
                atomicReference.compareAndSet(100, 101);
                atomicReference.compareAndSet(101, 100);
            }, "t1").start();
    
            new Thread(() -> {
                try {
                    // t2线程暂停1秒,确保t1线程完成了ABA操作
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 只认值,不认是否经历过ABA
                System.out.println(atomicReference.compareAndSet(100, 2019)
                    + ",t2当前值" + atomicReference.get());
            }, "t2").start();
    
    
            // 2.以下是ABA问题的解决
            new Thread(() -> {
                int stamp = atomicStampedReference.getStamp();// 版本号
                System.out.println("t3版本号" + stamp);
                // 暂停1秒钟t3线程,确保t4拿到相同的版本号
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 期望值、修改值、目前版本号、修改后版本号
                atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
                atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            }, "t3").start();
    
            new Thread(() -> {
                int stamp = atomicStampedReference.getStamp();// 版本号
                System.out.println("t4版本号" + stamp);
                // 暂停3秒钟t4线程,确保t3完成ABA
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 因为版本号已经被t3修改,所以这里比较并替换失败
                boolean b = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
                System.out.println(b + ",t4" + atomicStampedReference.getStamp() + "," + atomicStampedReference.getReference());
            }, "t4").start();
    
        }
    }

    展开全文
  • 什么是CAS机制?如何解决ABA问题?

    千次阅读 2019-08-27 16:20:17
    你知道什么是CAS机制吗?CAS和Synchronized的区别是什么?适用场景呢?

    你知道什么是CAS机制吗?CAS和Synchronized的区别是什么?适用场景呢?优点与缺点呢?

    我们先来看一手代码:

    启动两个线程,每个线程中让静态变量count循环累加100次。

    在这里插入图片描述

    该代码输出结果如下。因为这段代码是线程不安全的,所以自增结果很可能会小于200.
    在这里插入图片描述


    我们加上synchronized同步锁,再来看一下。

    在这里插入图片描述

    输出结果如下:
    在这里插入图片描述

    加了同步锁后,count自增的操作变成了原子性操作,所以最终输出结果一定是200,代码实现了线程安全。虽然synchronized确保了线程的安全,但是在有些情况下,这并不是最好的选择。

    关键在于性能问题。

    synchronized关键字会让没获得锁资源的线程进入BLOCKED(阻塞)状态,只有在争夺到锁资源的时候才转换成RUNNABLE(运行)状态。这其中涉及到操作系统中用户模式和内核模式之间的切换,代价比较高。

    同时,尽管jdk对synchronized关键字进行了优化,增加了从偏向锁到轻量级锁再到重量级锁的过度,但是在最终转变为重量级锁之后,性能依然比较低,所以面对这种只对单个变量进行原子性的操作,最好使用jdk自带的“原子操作类”。


    原子操作类,指的是java.util.concurrent.atomic包下,一系列以Atomic开头的包装类。例如AtomicBoolean、AtomicInteger、AtomicXXX都是分别对应Boolean、Integer或其他类型的原子性操作

    现在我们采用AtomicInteger类试一下:

    在这里插入图片描述

    输出结果如下:
    在这里插入图片描述

    使用原子操作类之后,最终的输出结果同样是200,保证了线程安全。并且在某种情况下,该方案代码的性能会比synchronized更好。

    而Atomic操作类的底层正是用到了"CAS机制"。

    首先,CAS的英文单词是Compare and Swap,即是比较并替换。

    CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,需要替换的值B。

    它的规则是:当需要更新一个变量的值的时候,只有当变量的预期值A和内存地址V中的实际值相同的时候,才会把内存地址V对应的值替换成B。

    我们可以来看一个例子:

    1.在内存地址V当中,存储着值为10的变量

    在这里插入图片描述

    2.此时线程1想要把变量的值增加1,对于线程1而言,它旧的预期值A=10,需要替换的最新值B=11。
    在这里插入图片描述

    3.在线程1要提交更新之前,另外一个线程2抢先一步,将内存地址V中的值更新成了11。

    在这里插入图片描述

    4.线程1开始提交更新的时候,按照CAS机制,首先进行A的值与内存地址V中的值进行比较,发现A不等于V中的实际值,于是提交失败。

    在这里插入图片描述
    5.线程1重新获取内存地址V的当前值,并重新计算想要修改的值。在现在而言,线程1旧的预期值A=11,B=12.这个重新尝试的过程被称为自旋
    在这里插入图片描述
    6.这一次比较幸运,没有其他线程改变该变量的值,所以线程1进行CAS机制,比较旧的预期值A与内存地址V中的值,发现相同,此时可以替换。
    在这里插入图片描述

    7.线程1进行替换,把地址V的值替换成B,也就是12.

    在这里插入图片描述

    从思想上来看,synchronized属于悲观锁,悲观的认为程序中的并发问题十分严重,所以严防死守,只让一个线程操作该代码块。而CAS属于乐观锁,乐观地认为程序中的并发问题并不那么严重,所以让线程不断的去尝试更新。

    在java中除了上面提到的Atomic操作类,以及Lock系列类的底层实现,甚至在jdk1.6以上,在synchronized转变为重量级锁之前,也会采用CAS机制。

    CAS的优点自然是在并发问题不严重的时候性能比synchronized要快,缺点也有。

    CAS的缺点:

    1.CPU开销过大

    在并发量比较高的时候,如果许多线程都尝试去更新一个变量的值,却又一直比较失败,导致提交失败,产生自旋,循环往复,会对CPU造成很大的压力和开销。

    2.不能确保代码块的原子性(注意是代码块)

    CAS机制所确保的是一个变量的原子性操作,而不能保证整个代码块的原子性,比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized或者lock了。

    3.ABA问题

    这就是CAS最大的问题所在。下面说。

    讲解了什么是CAS机制、CAS与synchronized的区别、它的优点和缺点之后。
    下面我们来介绍两个问题:

    1.JAVA中CAS的底层实现。

    2.CAS的ABA问题和解决方案。


    我们使用idea查看一下AtomicInteger中常用的自增方法incrementAndGet

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

    incrementAndGet调用的是unsafe的getAndAddInt方法并增加1

    在这里插入图片描述
    其中var1是当前对象,var2是内存地址V中的值,var6是旧的期望值A,var6+var4则是需要替换的值B。

    可以看到,这一段代码是一个无限循环,也就是CAS的自旋,循环体中做了三件事:
    1.获取当前的值,该方法使用native实现,底层是用其他语言实现的。

    2.当前值+var4,var4就是上一个方法传进来的1,计算出目标值B

    3.进行CAS操作,如果成功交换则跳出循环,如果失败则重复以上步骤。

    那我们怎么保证valueOffset也就是var2是正确的内存中最新的值的呢?很简单,用volatile关键字来保证线程间的可见性,也就保证了是最新的值。

    可以看到我们的代码始终和unsafe这个类相关,那什么是unsafe呢?java语言不像C,C++语言一样可以直接访问底层操作系统,但是JVM为我们开了一个后门,这个后门就是unsafe,unsafe为我们提供了硬件级别的原子操作。

    至于valueOffset变量则是通过unsafe的objectFieldOffset获得,所代表的就是AtomicInteger对象value成员变量在内存中的偏移量。我们可以简单的把valueOffset理解成value变量的内存地址也就是V了。

    我们前面说过,CAS机制中使用了三种基本操作数:内存地址V,旧的期望值A,新的需要替换的值B。

    而unsafe的compareAndSwapXxx方法中的参数var2则就代表valueOffset(内存地址V),var6代表A,var6+var4代表B。

    正是unsafe的compareAndSwapXxx方法保证了比较和替换之间的原子性操作!


    现在我们来说下什么是ABA问题。

    1.假设内存中有一个值为A的变量,存储在内存地址V中。
    在这里插入图片描述

    2.此时有三个线程想要使用CAS的方式更新这个变量的值,每个线程的执行时间有略微偏差。线程1和线程2已经获取当前值,线程3还未获取当前值。
    在这里插入图片描述

    3.接下来,线程1先一步执行成功,把当前值成功从A更新为B;同时线程2因某种原因阻塞住,没有做更新操作,此时线程3在线程1更新之后,获取了当前值B。
    在这里插入图片描述

    4.在之后,线程2仍然处于阻塞状态,线程3继续执行,成功把当前值从B更新成A。
    在这里插入图片描述

    5.最后,线程2终于恢复了运行状态,由于阻塞之前已经获得到了”当前值A“,并且经过compare检测,内存地址V中的实际值也是A,所以成功把变量A的值更新为B。

    在这里插入图片描述

    看起来这个例子没有什么问题,但如果结合实际,就可以发现它的问题所在。

    我们假设一个取款机的例子。假如有一个遵循CAS机制的取款机。小肖有100元存款,需要提取50元。但由于取款机硬件出现了问题,导致取款操作同时提交了两遍,开启了两个线程,两个线程都是获取当前值100元,要更新成50元。

    理想情况下,应该一个线程更新成功,一个线程更新失败,小肖的存款只扣除一次,余额为50.

    在这里插入图片描述

    线程1首先执行成功,把余额100更新为50,同时线程2由于某种原因陷入了阻塞状态,这时候,小肖的妈妈汇款给了小肖50元。

    在这里插入图片描述

    线程2仍然是阻塞状态,线程3此时执行成功,把余额从50改成了100.

    在这里插入图片描述

    这时候,线程2恢复运行,由于之前阻塞的时候获得了”当前值“100,并且经过compare检测,此时存款也的确是100元,所以成功把变量值从100更新成50.

    在这里插入图片描述

    原本线程2应当提交失败,小肖的正确余额应该保持100元,结果由于ABA问题提交成功了。

    这就是所谓的ABA问题,那么怎么解决呢?

    添加版本号解决ABA问题

    真正要做到严谨的CAS机制,我们在compare阶段不仅需要比较内存地址V中的值是否和旧的期望值A相同,还需要比较变量的版本号是否一致。

    我们仍然以刚才的例子来说明,假设地址V中存储着变量值A,当前版本号是01.线程1获取了当前值A和版本号01,想要更新为B,此时线程1陷入了阻塞状态。

    在这里插入图片描述

    这时候,内存地址V中的变量进行了多次改变,版本号提升到03,但是变量值仍然是A。

    在这里插入图片描述

    随后,线程1恢复运行,进行compare操作。首先经过比较,内存地址V中的值与当前值A相同,但是版本号不相同,所以这一次更新失败。

    在这里插入图片描述

    在Java中,AtomicStampedReference类就实现了用版本号做比较的CAS机制。

    1.Java语言CAS底层是如何实现的?

    答:利用unsafe提供的原子性操作方法。

    2.什么是ABA问题?怎么解决?

    答:当一个值从A更新为B,再从B更新为A,普通CAS机制会误判通过检测。解决方案是使用版本号,通过比较值和版本号才判断是否可以替换。

    展开全文
  • 什么是CAS? 在计算机科学中,比较和交换(Compare And Swap)是用于实现多线程同步的原子指令。 它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。 这是作为单个...
  • 单点登录学习笔记: 单点登录简介 SSO&CAS什么 单点登录适合什么场景 单点登录的三种实现方式 CAS的几个重要知识点 CAS的实现过程 ...
  • 漫画:什么是CAS机制?(进阶篇)

    千次阅读 多人点赞 2018-01-08 00:00:00
    上一期为大家讲解的CAS机制的基本概念,没看过的小伙伴们可以点击下面的链接:漫画:什么是 CAS 机制?这一期我们来深入介绍之前遗留的两个问题:Java当中CAS的底层实现CAS的ABA问题和解决方法首先看一看...
  • Java 什么是 CAS? 通俗易懂

    千次阅读 多人点赞 2019-05-15 17:25:24
    Java 并发机制实现原子操作有两种: 一种是锁,一种是CASCAS是Compare And Swap(比较并替换)的缩写。 java.util.concurrent.atomic中的很多类,如(AtomicInteger AtomicBoolean AtomicLong等)都使用了CAS...
  • 什么是 CAS 机制?

    千次阅读 2018-01-02 15:18:17
    CAS机制的简单介绍
  • 什么是CASCAS的作用以及缺点

    千次阅读 2019-06-03 14:10:47
    CAS原理就是对v对象进行赋值时,先判断原来的值是否为A,如果为A,就把新值B赋值到V对象上面,如果原来的值不是A(代表V的值放生了变化),就不赋新值。 小伙伴们应该知道j.u.c并发编程包,我们看一下...
  • 漫画:什么是 CAS 机制?

    千次阅读 多人点赞 2018-01-02 00:00:00
    ————— 第二天 ——...最终输出的count结果是什么呢?一定会是200吗?加了同步锁之后,count自增的操作变成了原子性操作,所以最终的输出一定是count=200,代码实现了线程安全。为什么这么说呢?关键在于性能问题
  • 什么是CAS机制?(进阶篇)

    千次阅读 2018-04-12 15:18:36
    转载自 永远爱大家的 程序员小灰这一期我们来深入介绍之前遗留的两个问题:Java当中CAS的底层实现CAS的ABA问题和解决方法首先看一看AtomicInteger当中常用的自增方法 incrementAndGet:public final int ...
  • 一.概念及特性 CAS(Compare And Swap/Set)比较并交换,CAS 算法的过程是这样:它包含 3 个参数 CAS(V,E,N)。V 表示要更新的变量(内存值),E 表示预期值(旧...前线程什么都不做。最后,CAS 返回当前 V 的真实值。 C...
  • CAS lock cmpxchg在硬件层面实现:在操作过程中不允许被其他CPU打断,避免CAS在写数据的时候被其他线程打断,相比操作系统级别的锁,效率要高很多。 加锁才能让多线程的访问变为序列化。 用户态和内核态 对于...
  • 一、悲观锁 1.基本概念 每次获取数据的时候,都会担心数据被修改,所以每次获取数据的时候都会进行加锁,确保在自己使用的过程中数据不会被别人修改,使用完成后进行数据解锁。...什么是CAS机制?
  • CAS 包括两部分: CAS Server 和 CAS Client  CAS Server:负责完成对用户的认证工作 , 需要独立部署。 CAS Client:负责处理对客户端受保护资源的访问请求,需要对请求方进行身份认证时,重定向到 CAS Server ...
  • CAS

    2021-01-06 23:29:49
    CAS什么 CAS的全称为Compare-And-Swap,它是一条CPU并发原语 它的功能是判断内存某个位置的值是否为预期值,如果是则改为新的值,这个过程是原子的。 CAS并发原语体现在Java语言中就是sun.misc.Unsafe类中的各个...
  • 什么是java中的CAS

    万次阅读 多人点赞 2018-04-13 02:39:42
    问题一:java中的CAS什么?问题二:为什么要使用CAS?问题三:CAS使用中需要注意什么问题? 这里以提问的方式引出话题,下面带大家慢慢了解CAS。1.CAS的含义CAS是compare and swap的缩写,即我们所说的比较交。cas...
  • CAS什么

    千次阅读 2018-05-09 17:34:31
    首先第一个问题CAS什么CAS的Comple And Swap的缩写,简单翻译过来就是比较 并且 覆盖。在CAS机制中存在三个基本操作值,V内存值,A旧预期值,B预期值。举个例子如图:1、线程一进入内存值V为10,然后,老预期值A...
  • CAS什么?彻底搞懂CAS

    千次阅读 2020-05-10 09:08:29
    CAS(Compare-And-Swap),它是一条CPU并发原语,用于判断内存中某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
  • CAS什么

    千次阅读 2008-09-01 12:15:00
    CAS(Central Authentication Service),是耶鲁大学开发的单点登录系统(SSO,single sign-on),应用广泛,具有独立于平台的,易于理解,支持代理功能。CAS系统在各个大学如耶鲁大学、加州大学、剑桥大学、香港...
  • 什么是java中CAS

    千次阅读 2018-10-30 18:57:50
     本篇的思路是先阐明无锁执行者CAS的核心算法原理然后分析Java执行CAS的实践者Unsafe类,该类中的方法都是native修饰的,因此我们会以说明方法作用为主介绍Unsafe类,最后再介绍并发包中的Atomic系统使用CAS原理...
  • cas

    千次阅读 2014-06-30 23:52:04
    cas
  • CAS操作原理

    千次阅读 2020-07-16 16:57:23
    1、什么是CASCAS:Compare and Swap,即比较再交换。 jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。JDK 5之前Java语言是靠synchronized关键字...
  • cas client cas server

    2018-01-05 14:53:00
    cas服务器的原理以及介绍,CAS服务端总共对外暴露了7个接口,客户端通过访问这7个接口与服务端交互,
  • CAS5.3服务器搭建及SpringBoot整合CAS实现单点登录1.1 什么是单点登录1.2 什么是CAS1.3 CAS服务端部署1.template下载1.4 客户端搭建 1.1 什么是单点登录 单点登录(Single Sign On),简称为 SSO,是目前比较流行的...
  • cas 配置

    2018-06-27 17:18:20
    cas 配置 cas 配置 cas 配置 cas 配置 cas 配置 cas 配置 cas 配置 cas 配置 cas 配置 cas 配置 cas 配置 cas 配置
  • CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。 CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。 CAS的缺点: 1.CPU开销较大 在并发量比较高的情况下,如果许多线程...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 213,575
精华内容 85,430
关键字:

什么是cas