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

相關文章
相關標籤/搜索