在上次的一文看懂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,後續來寫多線程相關文章也會容易些了,感謝各位支持!