视频课程
小黑屋思过中,禁止观看!
评论并刷新后可见

您需要在视频最下面评论并刷新后,方可查看完整视频

视频课程
立即观看
付费视频

您支付费用,方可查看完整视频

¥{{user.role.value}}
课程视频
开始学习
会员专享

视频合集

CAS实现原理详解

  • 课程笔记
  • 问答交流

在并发编程中,多线程竞争同一个资源时,一般我们常用的是synchronized等排它锁来解决多线程的资源竞争。

synchronized属于有锁的解决方案,但是加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。

是否可以采用无锁的方式来解决多线程竞争?

CAS就是一个无锁解决方案,更准确的是采用乐观锁技术,但并不是说CAS的方式就是性能最好的,无锁也有它的劣势,文末会谈到它的劣势与应用场景。

为了助大家掌握好CAS,本节课我会重点讲解以下6点:

1.CAS
2.CAS的实现原理
3.CAS的优缺点
4.CAS自旋
5.CAS的ABA问题
6.CAS的总结

什么是CAS

CAS(Compare and swap),即比较并交换,也是实现我们平时所说的自旋锁或乐观锁的核心操作。

Java并发包中的很多类都使用了CAS技术,是实现我们平时所说的自旋锁或乐观锁的核心操作。

CAS的算法

它的实现很简单,就是用一个预期的值和内存值进行比较,如果两个值相等,就用预期的值替换内存值,并返回 true。否则,返回 false。

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

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

CAS的实现原理

CAS实现原理详解-mikechen

如上图显示,两个线程:线程1和线程2同时修改值,如果通过CAS来实现,具体流程如下:

  1. 在内存地址V当中,存储着值为7的变量
  2. 线程1想要把变量的值增加1,对线程1来说,旧的预期值A=7,要修改的新值B=8
  3. 线程2抢先一步,把内存值V修改:8
  4. 线程1开始提交更新,首先对比了预期值A=7和实际值V的比较8(Compare),发现A不等于V的实际值,提交失败

CAS的优缺点

1.CAS的优点

非阻塞的轻量级的乐观锁,通过CPU指令实现,在资源竞争不激烈的情况下性能高

2.CAS的三大缺点:

1)自旋(循环)时间过长,消耗CPU资源,如果资源竞争激烈,多线程自旋长时间消耗资源

2)只能保证一个共享变量的原子操作,不能保证代码块的原子性。比如需要保证3个变量共同进行原子性的更新,需要Synchronized解决。从Java1.5开始JDK提供了

3)AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

CAS算法在JDK中的应用

在原子类变量中,如java.util.concurrent.atomic中的AtomicXXX,都使用了这些底层的JVM支持为数字类型的引用类型提供一种高效的CAS操作,而在java.util.concurrent中的大多数类在实现时都直接或间接的使用了这些原子变量类。

Java 中AtomicInteger.incrementAndGet()的实现源码为:

AtomicInteger:递增计数器实现
CAS实现原理详解-mikechen

该方法采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。

CAS的ABA问题

CAS实现原理详解-mikechen
线程1准备用CAS修改变量值A
在此之前,线程2将变量的值由A替换为B,又由B替换为A,然后线程1执行CAS时发现变量的值仍然为A,所以CAS成功,但实际上这时的现场已经和最初不同了。

解决方案
java的原子类AtomicStampedReference,通过控制变量值的版本号来保证CAS的正确性。具体解决思路就是在变量前追加上版本号,每次变量更新的时候把版本号加一,那么A – B – A就会变成1A – 2B – 3A。

CAS总结

CAS特点:不断比较更新,直到成功。

CAS的优点:非阻塞的轻量级的乐观锁,通过CPU指令实现,在资源竞争不激烈的情况下性能高。

CAS缺点:高并发cpu压力大;ABA问题。

CAS适用场景:乐观认为并发不高,不需要阻塞,可以不上锁。

评论交流
  1. JansenZhang

    基于CAS实现的乐观锁在低并发的情况下会比synchronized的效率高,因为不存在加锁解锁的操作。
    但是在多并发的情况下,多个线程都在进行自旋,会导致cpu负载过高。
    synchronized主要是在切换线程时会发生用户态和内核态的切换,也会导致消耗比较多的资源。
    在使用的时候,如果并发量不大,推荐CAS锁。

  2. 路正银

    CAS乐观锁,总是假设最好的情况,每次去读数据的时候都人为别人不会修改,所以不会上锁,但是在更新的时候
    会判断一下在此期间有没有其他线程更新该数据。
    悲观锁,总是假设最坏的情况,每次去读数据的时候都人为别人会修改,所以每次在读数据的时候都会上锁,这样别人想读取数据就会阻塞直到它获得锁。

    乐观锁的缺点:
    1、ABA问题
    2、自旋时间长开销大
    3、只能保证一个共享变量的原子操作

    悲观锁的缺点:
    1、需要独占资源,
    2、会发生线程阻塞
    3、线程之间切换会发费时间

    乐观锁和悲观锁在部分场景下可以看做相对的两面,优缺点恰好是对照的,一个的缺点恰好是另一个的优点

    乐观锁的悲观锁的优缺点决定了他们的适用场景
    乐观锁适用于写比较少的情况下,在这种场景下用乐观锁可以省去锁的开销,加大系统的吞吐量
    悲观锁适用于多写场景,在多写的情况下,一般会经常产生冲突,这时候用乐观锁会不断的进行自旋,反倒是降低了性能
    资源竞争较少的场景,用乐观锁相比较于悲观锁,可以减少线程之间切换的时间
    资源竞争严重的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于悲观锁

  3. 李鸿翼

    1.CAS乐观锁,总是假设最好的情况,每次去读数据都认为别人不会修改,所以不会上锁,但更新的时候需要判断该数据是否被人修改过,如果被修改过,则不进行数据更新,如果没有被修改过,进行更新。

    2.synchronized悲观锁,每次获取数据都担心被修改,所以每次获取数据都会进行加锁,使用完后会解锁。在加锁期间,其他对该数据进行读写的线程都会进行等待。

    乐观锁适用于读多写少,并发冲突少的场景;悲观锁适合强一致性的场景,但效率比较低,特别是读的并发低。

    对于资源竞争较少,可以使用乐观锁,因为CAS基于硬件实现,不会进入内核,不需要切换线程,操作自选几率较少,可以获得较高性能;而使用synchronized的悲观锁会进行线程阻塞和唤醒切换,用户态和内核态切换操作额外消费CPU资源。

    对于资源竞争较多,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized;

    但是在jdk1.6后,synchronized进行了优化,基于Lock-free的队列,基本思路是自旋后阻塞。在线程冲突较少的情况下,可以获得和CAS类似的性能;当线程冲突严重的请款改下,性能高于cas