Java原子類實現原理分析

AskHarries發表於2018-04-27

在談談java中的volatile一文中,我們提到過併發包中的原子類可以解決類似num++這樣的複合類操作的原子性問題,相比鎖機制,使用原子類更精巧輕量,效能開銷更小,本章就一起來分析下原子類的實現機理。

悲觀的解決方案(阻塞同步)

我們知道,num++看似簡單的一個操作,實際上是由1.讀取 2.加一 3.寫入 三步組成的,這是個複合類的操作(所以我們之前提到過的volatile是無法解決num++的原子性問題的),在併發環境下,如果不做任何同步處理,就會有執行緒安全問題。最直接的處理方式就是加鎖

synchronized(this){
    num++;
 }複製程式碼

使用獨佔鎖機制來解決,是一種悲觀的併發策略,抱著一副“總有刁民想害朕”的態勢,每次運算元據的時候都認為別的執行緒會參與競爭修改,所以直接加鎖。同一刻只能有一個執行緒持有鎖,那其他執行緒就會阻塞。執行緒的掛起恢復會帶來很大的效能開銷,儘管jvm對於非競爭性的鎖的獲取和釋放做了很多優化,但是一旦有多個執行緒競爭鎖,頻繁的阻塞喚醒,還是會有很大的效能開銷的。所以,使用synchronized或其他重量級鎖來處理顯然不夠合理。

樂觀的解決方案(非阻塞同步)

樂觀的解決方案,顧名思義,就是很大度樂觀,每次運算元據的時候,都認為別的執行緒不會參與競爭修改,也不加鎖。如果操作成功了那最好;如果失敗了,比如中途確有別的執行緒進入並修改了資料(依賴於衝突檢測),也不會阻塞,可以採取一些補償機制,一般的策略就是反覆重試。很顯然,這種思想相比簡單粗暴利用鎖來保證同步要合理的多。

鑑於併發包中的原子類其實現機理都差不太多,本章我們就通過AtomicInteger這個原子類來進行分析。我們先來看看對於num++這樣的操作AtomicInteger是如何保證其原子性的。

 /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }複製程式碼

我們來分析下incrementAndGet的邏輯:

1.先獲取當前的value值

2.對value加一

3.第三步是關鍵步驟,呼叫compareAndSet方法來來進行原子更新操作,這個方法的語義是:

先檢查當前value是否等於current,如果相等,則意味著value沒被其他執行緒修改過,更新並返回true。如果不相等,compareAndSet則會返回false,然後迴圈繼續嘗試更新。

  compareAndSet呼叫了Unsafe類的compareAndSwapInt方法

/**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return true if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }複製程式碼

Unsafe的compareAndSwapInt是個native方法,也就是平臺相關的。它是基於CPU的CAS指令來完成的。

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);複製程式碼

CAS(Compare-and-Swap)  

CAS演算法是由硬體直接支援來保證原子性的,有三個運算元:記憶體位置V、舊的預期值A和新值B,當且僅當V符合預期值A時,CAS用新值B原子化地更新V的值,否則,它什麼都不做。

CAS的ABA問題

當然CAS也並不完美,它存在”ABA”問題,假若一個變數初次讀取是A,在compare階段依然是A,但其實可能在此過程中,它先被改為B,再被改回A,而CAS是無法意識到這個問題的。CAS只關注了比較前後的值是否改變,而無法清楚在此過程中變數的變更明細,這就是所謂的ABA漏洞。

Java原子類實現原理分析



相關文章