在上次的一文看懂CouncurrentHashMap里面对CAS作了一个简单的介绍,以后打算着手写的java.util.concurrent包下的AQS以及其它一些都用到了CAS算法,所以今天就来深刻研究一下,本文会介绍如下几个问题:java
CAS能够看作是乐观锁
的一种实现方式,Java原子类中的递增操做就经过CAS自旋实现的。
CAS全称Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的状况下实现多线程之间的变量同步。算法
CAS涉及到三个属性:segmentfault
CAS具体执行时,当且仅当预期值A符合内存地址V中存储的值时,就用新值U替换掉旧值,并写入到内存地址V中。不然不作更新。数组
那么在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();
incrementAndGet
是Unsafe类中的方法,它以原子方式对当前值加1,源码以下:性能
//以原子方式对当前值加1,返回的是更新后的值 public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }
this
指的当前AtomicInteger对象valueOffset
是value的内存偏移量(new AtomicInteger()默认value值为0)1
指的就是对value加1ps:最后还要+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)
若是原子变量中的 value 值等于 expect,则使用 update 值更新该值并返回 true,不然返回 false。
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,后续来写多线程相关文章也会容易些了,感谢各位支持!