java 併發包之 LongAdder 源碼分析

簡介

LongAdder是java8中新增的原子類,在多線程環境中,它比AtomicLong性能要高出不少,特別是寫多的場景。

它是怎麼實現的呢?讓我們一起來學習吧。

原理

LongAdder的原理是,在最初無競爭時,只更新base的值,當有多線程競爭時通過分段的思想,讓不同的線程更新不同的段,最後把這些段相加就得到了完整的LongAdder存儲的值。

源碼分析

LongAdder繼承自Striped64抽象類,Striped64中定義了Cell內部類和各重要屬性。

主要內部類

Cell類使用@sun.misc.Contended註解,說明是要避免僞共享的。

使用Unsafe的CAS更新value的值,其中value的值使用volatile修飾,保證可見性。

關於Unsafe的介紹請查看【死磕 java魔法類之Unsafe解析】。

關於僞共享的介紹請查看【雜談 什麼是僞共享(false sharing)?】。

主要屬性

最初無競爭或有其它線程在創建cells數組時使用base更新值,有過競爭時使用cells更新值。

最初無競爭是指一開始沒有線程之間的競爭,但也有可能是多線程在操作,只是這些線程沒有同時去更新base的值。

有過競爭是指只要出現過競爭不管後面有沒有競爭都使用cells更新值,規則是不同的線程hash到不同的cell上去更新,減少競爭。

add(x)方法

add(x)方法是LongAdder的主要方法,使用它可以使LongAdder中存儲的值增加x,x可爲正可爲負。


(1)最初無競爭時只更新base;

(2)直到更新base失敗時,創建cells數組;

(3)當多個線程競爭同一個Cell比較激烈時,可能要擴容;

sum()方法

sum()方法

可以看到sum()方法是把base和所有段的值相加得到,那麼,這裏有一個問題,如果前面已經累加到sum上的Cell的value有修改,不是就沒法計算到了麼?

答案確實如此,所以LongAdder可以說不是強一致性的,它是最終一致性的。

LongAdder VS AtomicLong

當只有一個線程的時候,AtomicLong反而性能更高,隨着線程越來越多,AtomicLong的性能急劇下降,而LongAdder的性能影響很小。

總結

(1)LongAdder通過base和cells數組來存儲值;

(2)不同的線程會hash到不同的cell上去更新,減少了競爭;

(3)LongAdder的性能非常高,最終會達到一種無競爭的狀態;

彩蛋

在longAccumulate()方法中有個條件是 n>=NCPU就不會走到擴容邏輯了,而n是2的倍數,那是不是代表cells數組最大隻能達到大於等於NCPU的最小2次方?

答案是明確的。因爲同一個CPU核心同時只會運行一個線程,而更新失敗了說明有兩個不同的核心更新了同一個Cell,這時會重新設置更新失敗的那個線程的probe值,這樣下一次它所在的Cell很大概率會發生改變,如果運行的時間足夠長,最終會出現同一個核心的所有線程都會hash到同一個Cell(大概率,但不一定全在一個Cell上)上去更新,所以,這裏cells數組中長度並不需要太長,達到CPU核心數足夠了。

比如,筆者的電腦是8核的,所以這裏cells的數組最大只會到8,達到8就不會擴容了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章