Java Concurrency Series - CAS Detailed Explanation

Java Concurrency Series - CAS Detailed Explanation

CAS

What is CAS

CAS means comparison and exchange, which is a CPU atomic instruction. CAS implements an optimistic lock that is different from sychronized synchronization locks (synchronise is a pessimistic lock idea, a kind of mutual exclusion lock, I will grab the resource when I get it. In my hand, wait for me to release the others, and CAS is a retry mechanism, and if it fails, try again), when multiple threads try to use CAS to change a variable, only one thread can update the value of the variable, the other Threads all fail, the failed thread is not suspended, it is told that the race has failed, and it tries again. Although comparison and exchange are two actions, CAS guarantees the atomicity of comparison and exchange at the hardware level. In fact, CAS also has a lock operation, but this lock is realized by the CPU. The CPU uses the cache lock and The way of bus locking guarantees atomic operation between multiprocessing. Compared with Synchronise, the effect of object plus monitor is much better.

Think about the java memory model here. Shared variables are stored in main memory, and each thread's working memory saves a copy. The expected value in CAS is actually a copy of the variable. When a thread obtains a variable from the main memory, the value in the main memory may be refreshed by other threads. At this time, the value saved in the thread is inconsistent with the value in the main memory and fails. After the failure, the value is re-read from the main memory. Loop operation.

Example of use

The AutomicXXX series of classes are the best usage examples. Let's see how AutomicInter ensures data security without locks.

   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());
   }
复制代码

Let's take a look at the bottom layer of 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 problem

CAS is a spin lock, which has the following problems

ABA problem

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…

Guess you like

Origin juejin.im/post/7030002149362761758