CAS机制总结

Posted sunweiye

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CAS机制总结相关的知识,希望对你有一定的参考价值。

一、简介

CAS机制:(Compare and set)比较和替换

  简单来说–>使用一个期望值来和当前变量的值进行比较,如果当前的变量值与我们期望的值相等,就用一个新的值来更新当前变量的值
CAS有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时(条件),将内存值修改为B并返回true,否则条件不符合返回false。条件不符合说明该变量已经被其它线程更新了。

  当多个线程访问相同的数据时,如果使用锁来进行并发控制,当某一个线程(T1)抢占到锁之后,那么其他线程再尝试去抢占锁时就会被阻塞,当T1释放锁之后,下一个线程(T2)再抢占到锁后并且重新恢复到原来的状态线程从阻塞到重新准备运行有很长的时间开销。而假设我们业务代码本身并不具备很复杂的操作,并发量也不高,那么当我们使用CAS机制来代替加锁操作,当多个线程操作同一变量时每个线程会首先会读取到地址值对应的原值作为自己的期望值,然后进行操作,操作完以后在更新的时候他会判断现在地址值对应的值是否与自己的期望值相同,如果相同,就认为自己操作的过程中别人没有进行过操作。则将自己操作后的值更新到地址值对应的位置,如果不同则说明自己操作的过程中一定有其他的线程更新过数据,然后会把当前地址值对应的值返回给用户,用户拿到新值重新上次的操作。不断循环直到更新成功。

二、用途

CAS的使用场景:juc下ReentryLock 和 Atomic类操作

CAS乐观锁(循环内自旋):原理:A=内存值,thread1对A进行累加操作后的值为B。更新内存值时会判断A和B是否相等,如果相等,那么B替换A退出循环,如果不相等,重新获得内存值,进行操作。此套流程如何保证内存值是最新的?详见volatile原理此套流程如何保证V,A比较B替换V时是原子操作?cas底层用unsafe直接访问底层操作系统,做了硬件级别的原子操作。

AtomicInteger源码分析
java.util.concurrent.atomic包下的原子操作类都是基于CAS实现的,接下去我们通过AtomicInteger来看看是如何通过CAS实现原子操作的:

public class AtomicInteger extends Number implements java.io.Serializable 
    private static final long serialVersionUID = 6214790243416807050L;
    // setup to use Unsafe.compareAndSwapInt for updates
    //获得unsafe实例
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //存放变量value的内存偏移
    private static final long valueOffset;

    static 
        try 
            //通过unsafe获得value的内存偏移
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
         catch (Exception ex)  throw new Error(ex); 
    

    //volatile修饰的,保证了多线程之间看到的value值是同一份,后面会分析
    private volatile int value;

接下来看看设置新的值是如何完成的:

public final boolean compareAndSet(int expect, int update) 
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    

这里很简单,直接调用unsafe的cas方法就可以了,通过value的内存偏移,以及期望的值来设置新的值。接下来就是本地方法调用了。

 public final int getAndIncrement() 
        return unsafe.getAndAddInt(this, valueOffset, 1);
    

同样原子增加的操作就是通过unsafe来完成,只是每次都是增加1而已。
可以去看看其他几个原子操作类:AtomicLong(原子更新长整型)AtomicBoolean(原子更新布尔类型,用int来实现的),AtomicIntegerArray(原子更新整型数组里的元素),AtomicReference(原子更新引用类型)等,其核心思想都是用unsafe提供的原子操作来完成的。
在Java并发编程的艺术中,作者实现了一个基于CAS线程安全的计数器和一个非线程安全的计数器,本质就是用原子操作类代替一般的int或者Long数据类型,通过原子操作类来完成原子操作,保证了计数的线程安全,但是这里提一下,原子操作类不是什么时候都是线程安全的,当由原子操作类来完成复合操作是,此时就不一定是线程安全的了。

 AtomicInteger a=new AtomicInteger(10);
        //假设下面会出现线程竞争
        int b=a.get();
        if (b==10)
            a.compareAndSet(10,100);
        

在多线程中,这样就不是线程安全的了,因为先取出某个值,然后在判断,这整个操作不是原子操作。


 

三、优缺点

cas优点:

  如一描述在并发量不是很高时cas机制会提高效率。

cas缺点:

1.循环时间开销太大:

  如果CAS长时间执行不成功,则会给CPU带来交大的执行开销。处理器提供一种pause指令可以缓解这部分问题,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

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

  如果需要对多个共享变量进行同步,就得使用锁,或者将几个共享变量封装起来,使用CAS来进行同步。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作

3、ABA问题

aba问题:内存值V=100;
threadA 将100,改为50;
threadB 将100,改为50;
threadC 将50,改为100;

场景:小牛取款,由于机器不太好使,多点了几次全款操作。后台threadA和threadB工作,
此时threadA操作成功(100->50),threadB阻塞。正好牛妈打款50元给小牛(50->100),
threadC执行成功,之后threadB运行了,又改为(100->50)。
牛气冲天,lz钱哪去了???

如何解决aba问题:对内存中的值加个版本号,在比较的时候除了比较值还的比较版本号。

java:AtomicStampedReference就是用版本号实现cas机制。

以上是关于CAS机制总结的主要内容,如果未能解决你的问题,请参考以下文章

markdown [Apereo CAS 3.5 CORE] Apereo CAS 3.5 #CAS的核心代码片段

Java 锁机制总结

多线程 CAS 机制解析及应用( 原子类 . 自旋锁 )解决 ABA 问题

Java:java学习笔记之锁机制的简单理解和使用

Java:java学习笔记之锁机制的简单理解和使用

你知道的Go切片扩容机制可能是错的