AtomicLong 與 LongAdder(CAS機制的優化)

eluanshi12發表於2018-12-07

執行緒安全性-原子性-CAS
LongAdder是java8為我們提供的新的類,跟AtomicLong有相同的效果。是對CAS機制的優化。

AtomicLong:
//變數宣告
public static AtomicLong count = new AtomicLong(0);
//變數操作
count.incrementAndGet();
//變數取值
count.get();
LongAdder:
//變數宣告
public static LongAdder count = new LongAdder();
//變數操作
count.increment();
//變數取值
count

為什麼有了AtomicLong還要新增一個LongAdder呢?
原因是:CAS底層實現是在一個死迴圈中不斷地嘗試修改目標值,直到修改成功。如果競爭不激烈的時候,修改成功率很高,否則失敗率很高。在失敗的時候,這些重複的原子性操作會耗費效能。(不停的自旋,進入一個無限重複的迴圈中)

知識點: 對於普通型別的long、double變數,JVM允許將64位的讀操作或寫操作拆成兩個32位的操作。

核心思想:將熱點資料分離。
比如說它可以將AtomicLong內部的內部核心資料value分離成一個陣列,每個執行緒訪問時,通過hash等演算法對映到其中一個數字進行計數,而最終的計數結果則為這個陣列的求和累加,其中熱點資料value會被分離成多個單元的cell,每個cell獨自維護內部的值。當前物件的實際值由所有的cell累計合成,這樣熱點就進行了有效地分離,並提高了並行度。這相當於將AtomicLong的單點的更新壓力分擔到各個節點上。在低併發的時候通過對base的直接更新,可以保障和AtomicLong的效能基本一致。而在高併發的時候通過分散提高了效能。

原始碼:
public void increment() {
    add(1L);
}
public void add(long x) {
    Cell[] as; long b, v; int m; Cell a;
    if ((as = cells) != null || !casBase(b = base, b + x)) {
        boolean uncontended = true;
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[getProbe() & m]) == null ||
            !(uncontended = a.cas(v = a.value, v + x)))
            longAccumulate(x, null, uncontended);
    }
}

缺點:如果在統計的時候,如果有併發更新,可能會有統計資料有誤差。
實際使用中在處理高併發計數的時候優先使用LongAdder,而不是AtomicLong線上程競爭不激烈的時候,使用AtomicLong會簡單效率更高一些。比如序列號生成(準確性)


大白話聊聊Java併發面試問題之Java 8如何優化CAS效能?
LongAdder
在LongAdder的底層實現中,首先有一個base值,剛開始多執行緒來不停的累加數值,都是對base進行累加的,比如剛開始累加成了base = 5。

併發更新的執行緒數量過多時,施行分段CAS的機制(Cell陣列,每個陣列是一個數值分段)

這時,讓大量的執行緒分別去對不同Cell內部的value值進行CAS累加操作,這樣就把CAS計算壓力分散到了不同的Cell分段數值中了!

這樣就可以大幅度的降低多執行緒併發更新同一個數值時出現的無限迴圈的問題,大幅度提升了多執行緒併發更新數值的效能和效率!

而且他內部實現了自動分段遷移的機制,也就是如果某個Cell的value執行CAS失敗了,那麼就會自動去找另外一個Cell分段內的value值進行CAS操作。

這樣也解決了執行緒空旋轉、自旋不停等待執行CAS操作的問題,讓一個執行緒過來執行CAS時可以儘快的完成這個操作。

最後,如果你要從LongAdder中獲取當前累加的總值,就會把base值和所有Cell分段數值加起來返回給你。

相關文章