java并发系列-CAS详解

java并发系列-CAS详解

CAS

什么是CAS

​ CAS意思为比较交换,是一条CPU原子指令,CAS实现了区别于sychronized同步锁中的一种乐观锁(synchronise是一种悲观锁思想,是一种互斥锁,我拿到资源就会攥在手里,其他的等着我释放吧,而CAS是重试机制,失败就再次重试),多个线程尝试使用CAS改变某一个变量的时候,只有其中一个线程能够更新变量的值,其它线程都会失败,失败的线程不会被挂起,而是告知这次竞争失败,并且再次尝试。虽然比较交换是两个动作,但是CAS在硬件层面上保证了比较并交换的原子性,其实CAS也算有锁操作的,只不过这个锁是有CPU来实现的,CPU使用对缓存加锁和总线加锁的方式保证多处理之间的原子操作。 相对于Synchronise来说对象加monitor来说,效果要好的多.

这里可以想一下java内存模型,共享变量存在主内存中,每个线程工作内存保存一个副本,CAS中的预期值其实就是变量的副本。当线程从主内存中获取变量的时候,主内存中的值可能被其他线程刷新,这个时候线程中保存的值和主内存中的值不一致失败,失败之后重新从主内存中读取值,进行循环操作。

使用示例

AutomicXXX系列类是最好的使用示例,我们看下AutomicInter中是如何在无锁情况下保证数据安全性的。

   public static void main(String[] args) {
        AtomicInteger atomicInteger=new AtomicInteger(8);
        //当前值为8,期望值为8,改成10 成功  输出true
        System.out.println(atomicInteger.compareAndSet(8,10)+"目前的值为"+atomicInteger.get());
        //当前值为10,期望值为8 改成20 失败 输出false
        System.out.println(atomicInteger.compareAndSet(8,20)+"目前的值"+atomicInteger.get());
   }
复制代码

我们一起看下compareAndSet的底层

  public final boolean compareAndSet(int expect, int update) {
        //本地方法,调用CAS源语实现,CPU指令保证原子性,一直等待取到的值
        /**
        * 1:this代表的是unsafe这个类
        * 2:valueOffset:偏移量 valueoffset偏移量(从哪里来看下面的类说明),其实取到的就是目标值
        * 3:expect:期望的值
        * 4:update:更新的值
        *整句话实现的语意是如果this内的value(偏移量得到)和expect相等,就证明没有其他线程改变过这个变量,那么就更新它为update,				 
        * 没有成功的话没就采用自旋的方式继续进行CAS操作(感兴趣的小伙伴可以查看Hotspot源码具体试下过程,公众号程序员fly后面会出相			
        *文章,感兴趣的小伙伴可以关注下微信公众号)。
        **/
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

	/**
	*AtomicInteger类定义的变量和静态块
	*
	**/
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
           //valueOffset其实就是记录value的偏移量的
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

复制代码

CAS问题

CAS就是个自旋锁,存在以下几个问题

ABA问题

CAS在操作值的时候,会检查值有没有变化,比如原来有一个值是A,然后变成B,之后又变成了A,使用CAS技术进行检查的时候会发现值并没有发生变化,但实际其实是发生了变化的,这就是著名的CAS的ABA问题。常规的解决思路其实是加版本号每次变化的时候将版本号+1。整体变化过程就变成了1A-2B-3A。JDK在1.5之后的AtomicStampedReference就实现了这种实现来解决ABA问题。(AtomicMarkableReference也能解决ABA问题,它是通过一个boolean标记来标识是否有修改,不再是版本号,思想上其实都类似)

循环开销问题

自旋CAS如果长时间不成功的话,会给CPU带来很多开销。

保证一个共享变量原子操作

当对一个共享遍历操作的时候,我们可以循环使用CAS来保证原子操作,但是对多个共享遍历进行操作的时候,循环CAS无法保证原子性,但是有一个取巧的方法就是:多个共享变量合成一个变量进行操作,读过线程池源码同学的都知道,线程池中线程的数量以及线程的状态会合并成一个共享变量操作(线程池源码文章请关注公众号程序员fly了解相关详情)。

闲谈

感觉有帮助的同学还请点赞关注,这将对我是很大的鼓励~,公众号有自己开始总结的一系列文章,需要的小伙伴还请关注下个人公众号程序员fly呀,干货多多,湿货也不少(∩_∩)。

巨人肩膀

www.modb.pro/db/127003

juejin.cn/post/684490…

猜你喜欢

转载自juejin.im/post/7030002149362761758