java 并发包之 LongAdder 源码分析
?
前些天發(fā)現(xiàn)了一個(gè)巨牛的人工智能學(xué)習(xí)網(wǎng)站,通俗易懂,風(fēng)趣幽默,忍不住分享一下給大家。點(diǎn)擊跳轉(zhuǎn)到教程。
LongAdder是java8中新增的原子類,在多線程環(huán)境中,它比AtomicLong性能要高出不少,特別是寫(xiě)多的場(chǎng)景。
它是怎么實(shí)現(xiàn)的呢?讓我們一起來(lái)學(xué)習(xí)吧。
原理
LongAdder的原理是,在最初無(wú)競(jìng)爭(zhēng)時(shí),只更新base的值,當(dāng)有多線程競(jìng)爭(zhēng)時(shí)通過(guò)分段的思想,讓不同的線程更新不同的段,最后把這些段相加就得到了完整的LongAdder存儲(chǔ)的值。
源碼分析
LongAdder繼承自Striped64抽象類,Striped64中定義了Cell內(nèi)部類和各重要屬性。
主要內(nèi)部類
Cell類使用@sun.misc.Contended注解,說(shuō)明是要避免偽共享的。
使用Unsafe的CAS更新value的值,其中value的值使用volatile修飾,保證可見(jiàn)性。
關(guān)于Unsafe的介紹請(qǐng)查看【死磕 java魔法類之Unsafe解析】。
關(guān)于偽共享的介紹請(qǐng)查看【雜談 什么是偽共享(false sharing)?】。
主要屬性
最初無(wú)競(jìng)爭(zhēng)或有其它線程在創(chuàng)建cells數(shù)組時(shí)使用base更新值,有過(guò)競(jìng)爭(zhēng)時(shí)使用cells更新值。最初無(wú)競(jìng)爭(zhēng)是指一開(kāi)始沒(méi)有線程之間的競(jìng)爭(zhēng),但也有可能是多線程在操作,只是這些線程沒(méi)有同時(shí)去更新base的值。
有過(guò)競(jìng)爭(zhēng)是指只要出現(xiàn)過(guò)競(jìng)爭(zhēng)不管后面有沒(méi)有競(jìng)爭(zhēng)都使用cells更新值,規(guī)則是不同的線程hash到不同的cell上去更新,減少競(jìng)爭(zhēng)。
add(x)方法
add(x)方法是LongAdder的主要方法,使用它可以使LongAdder中存儲(chǔ)的值增加x,x可為正可為負(fù)。
(1)最初無(wú)競(jìng)爭(zhēng)時(shí)只更新base;
(2)直到更新base失敗時(shí),創(chuàng)建cells數(shù)組;
(3)當(dāng)多個(gè)線程競(jìng)爭(zhēng)同一個(gè)Cell比較激烈時(shí),可能要擴(kuò)容;
sum()方法
sum()方法
可以看到sum()方法是把base和所有段的值相加得到,那么,這里有一個(gè)問(wèn)題,如果前面已經(jīng)累加到sum上的Cell的value有修改,不是就沒(méi)法計(jì)算到了么?
答案確實(shí)如此,所以LongAdder可以說(shuō)不是強(qiáng)一致性的,它是最終一致性的。
LongAdder VS AtomicLong
當(dāng)只有一個(gè)線程的時(shí)候,AtomicLong反而性能更高,隨著線程越來(lái)越多,AtomicLong的性能急劇下降,而LongAdder的性能影響很小。
總結(jié)
(1)LongAdder通過(guò)base和cells數(shù)組來(lái)存儲(chǔ)值;
(2)不同的線程會(huì)hash到不同的cell上去更新,減少了競(jìng)爭(zhēng);
(3)LongAdder的性能非常高,最終會(huì)達(dá)到一種無(wú)競(jìng)爭(zhēng)的狀態(tài);
彩蛋
在longAccumulate()方法中有個(gè)條件是?n>=NCPU就不會(huì)走到擴(kuò)容邏輯了,而n是2的倍數(shù),那是不是代表cells數(shù)組最大只能達(dá)到大于等于NCPU的最小2次方?
答案是明確的。因?yàn)橥粋€(gè)CPU核心同時(shí)只會(huì)運(yùn)行一個(gè)線程,而更新失敗了說(shuō)明有兩個(gè)不同的核心更新了同一個(gè)Cell,這時(shí)會(huì)重新設(shè)置更新失敗的那個(gè)線程的probe值,這樣下一次它所在的Cell很大概率會(huì)發(fā)生改變,如果運(yùn)行的時(shí)間足夠長(zhǎng),最終會(huì)出現(xiàn)同一個(gè)核心的所有線程都會(huì)hash到同一個(gè)Cell(大概率,但不一定全在一個(gè)Cell上)上去更新,所以,這里cells數(shù)組中長(zhǎng)度并不需要太長(zhǎng),達(dá)到CPU核心數(shù)足夠了。
比如,筆者的電腦是8核的,所以這里cells的數(shù)組最大只會(huì)到8,達(dá)到8就不會(huì)擴(kuò)容了。
總結(jié)
以上是生活随笔為你收集整理的java 并发包之 LongAdder 源码分析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 利用FindWindow和SendMes
- 下一篇: 句柄的概念详解