天天看點

java 并發包之 LongAdder 源碼分析

前些天發現了一個巨牛的人工智能學習網站,通俗易懂,風趣幽默,忍不住分享一下給大家。點選跳轉到教程。

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

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

原理

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

java 并發包之 LongAdder 源碼分析

源碼分析

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

主要内部類

java 并發包之 LongAdder 源碼分析
Cell類使用@sun.misc.Contended注解,說明是要避免僞共享的。

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

關于Unsafe的介紹請檢視【死磕 java魔法類之Unsafe解析】。

關于僞共享的介紹請檢視【雜談 什麼是僞共享(false sharing)?】。

主要屬性

java 并發包之 LongAdder 源碼分析
最初無競争或有其它線程在建立cells數組時使用base更新值,有過競争時使用cells更新值。
           

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

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

add(x)方法

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

java 并發包之 LongAdder 源碼分析

(1)最初無競争時隻更新base;

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

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

sum()方法

java 并發包之 LongAdder 源碼分析

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就不會擴容了。

java 并發包之 LongAdder 源碼分析