CAS

CAS

一. 简介


利用锁保证线程安全会造成CPU资源浪费(例如线程调度,线程上下文切换等),考虑到锁带来的开销,引入了无锁算法CAS(Compare And Swpa,比较交换),成为整个JUC体系最核心、最基础理论。

CAS一定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会变的值A,只要某次CAS操作失败,永远都不可能成功。

  • 官方解释:我认为V的值应该是A,如果是,那么将V的值更新为B,否则不修改并告诉V的实际值是多少。其中:V代表内存值,A代表旧的预期值,B代表新的预期值。
  • CAS的目的在于判断欲修改的值是否被修改过。CAS的过程一旦被打断,就会重新开始。以确保操作的原子性。
  • 因为CAS涉及到线程的重新调度问题,所以需要结合具体的cpu内核架构来涉及,因此Java的CAS底层是依靠C语言实现的。目前市面上,服务器流行的内核架构都是支持CAS。

二. ABA问题


  • 问题:CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,后来变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。

  • 解决思路:使用版本号。在变量前面追加版本号,每次变量更新的时候把版本号+1,那么A->B->A 就会变成1A->2B->3A。从JDK1.5 开始,JDK的Atomic包里提供了一个类Atomic包里提供了一个AtomicStampedReference来解决ABA问题。

    public boolean compareAndSet(
    	V expectedReference, // 预期引用
    	V newReference, // 更新后的引用
    	int expectedStamp, // 预期标志
    	int newStamp // 更新后的标志
    )
    

    compareAndSet方法的作用首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

三. 缺陷


  1. 循环时间长,开销大

    CAS不成功则会一直自旋,如果自旋CAS长时间不成功,会给CPU带来非常大的执行开销。

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

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

    还有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如,有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java 1.5开始,JDK提供了AtomicReference类来保证引用对之间的原子性,就可以把多个变量放在一个对象里来进行CAS操作。

posted @ 2020-07-13 19:44  仰观云  阅读(12)  评论(0编辑  收藏
Live2D