一文完全搞懂CAS

在上次的一文看懂CouncurrentHashMap里面对CAS作了一个简单的介绍,以后打算着手写的java.util.concurrent包下的AQS以及其它一些都用到了CAS算法,所以今天就来深刻研究一下,本文会介绍如下几个问题:java

  • 什么是CAS
  • ABA问题
  • CAS优化

正文

CAS能够看作是乐观锁的一种实现方式,Java原子类中的递增操做就经过CAS自旋实现的。
CAS全称Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的状况下实现多线程之间的变量同步。算法

CAS涉及到三个属性:segmentfault

  • 须要读写的内存位置V
  • 须要进行比较的预期值A
  • 须要写入的新值U

CAS具体执行时,当且仅当预期值A符合内存地址V中存储的值时,就用新值U替换掉旧值,并写入到内存地址V中。不然不作更新。数组

CAS算法图解

源码实现

那么在JDK源码里面又是如何实现CAS算法的呢?多线程

CAS在JDK中是基于 Unsafe 类实现的,它是个跟底层硬件CPU指令通信的复制工具类,源码以下:并发

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

咱们通常用的比较多的就是上面3个本地方法,那么其中的参数又是什么意思呢?高并发

在JDK中使用CAS比较多的应该是Atomicxxx相关的类,咱们以AtomicInteger原子整型类为例,一块儿来分析下CAS底层实现机制:工具

AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.incrementAndGet();

incrementAndGetUnsafe类中的方法,它以原子方式对当前值加1,源码以下:性能

//以原子方式对当前值加1,返回的是更新后的值
 public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
 }
  • this指的当前AtomicInteger对象
  • valueOffset是value的内存偏移量(new AtomicInteger()默认value值为0)
  • 1指的就是对value加1

ps:最后还要+1是由于getAndAddInt方法返回的是更新前的值,而咱们要的是更新后的值优化

valueOffset内存偏移量就是用来获取value的,以下所示:

//获取unfase对象
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
private volatile int value;//实际变量的值
    static {
        try {// 得到value在AtomicInteger中的偏移量
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

value 是由 volatile 关键字修饰的,为了保证在多线程下的内存可见性。
从static代码块能够看到valueOffset在类加载时就已经进行初始化了。

最后来看看getAndAddInt方法

//var1-对象、var2-内存偏移量、var4-增长的值
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;//指望值
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
        
        return var5;
    }
//根据偏移量获取value    
public native int getIntVolatile(Object var1, long var2);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

源码也不是很复杂,主要搞懂各个参数的意思,getIntVolatile方法获取到指望值value后去调用compareAndSwapInt方法,失败则进行重试。

那来看看compareAndSwapInt方法:

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

老实说我也不知道为何参数要用var一、var2....表示,看起来的确不太好理解,我们换个参数名来从新看一下:

unsafe.compareAndSwapInt(this, valueOffset, expect, update)
  • this:Unsafe 对象自己,须要经过这个类来获取 value 的内存偏移地址。
  • valueOffset:value 变量的内存偏移地址。
  • expect:指望更新的值。
  • update:要更新的最新值。

若是原子变量中的 value 值等于 expect,则使用 update 值更新该值并返回 true,不然返回 false。

ABA

CAS也不是万能的,它也存在着一些问题,好比ABA,咱们来看看什么是ABA?

A-->B--->A 问题,假设有一个变量 A ,修改成B,而后又修改成了 A,实际已经修改过了,但 CAS 可能没法感知,形成了不合理的值修改操做。

为了解决这个 ABA 的问题,咱们能够引入版本控制,例如,每次有线程修改了引用的值,就会进行版本的更新,虽然两个线程持有相同的引用,但他们的版本不一样,这样,咱们就能够预防 ABA 问题了。Java 中提供了 AtomicStampedReference 这个类,就能够进行版本控制了。

开销

CAS算法须要不断地自旋来读取最新的内存值,当长时间读取不到就会形成没必要要的CPU开销。

Java 8推出了一个新的类LongAdder,他就是尝试使用分段CAS以及自动分段迁移的方式来大幅度提高多线程高并发执行CAS操做的性能!

简单来讲就是若是发现并发更新的线程数量过多,就会开始施行分段CAS的机制,也就是内部会搞一个Cell数组,每一个数组是一个数值分段。这时,让大量的线程分别去对不一样Cell内部的value值进行CAS累加操做,这样就把CAS计算压力分散到了不一样的Cell分段数值中了!感兴趣的同窗能够去查找相关资料。

总结

终于搞定CAS,后续来写多线程相关文章也会容易些了,感谢各位支持!

相关文章
相关标签/搜索