日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
從LongAdder看更高效的無鎖實(shí)現(xiàn)

接觸到AtomicLong的原因是在看guava的LoadingCache相關(guān)代碼時(shí),關(guān)于LoadingCache,其實(shí)思路也非常簡單清晰:用模板模式解決了緩存不命中時(shí)獲取數(shù)據(jù)的邏輯,這個(gè)思路我早前也正好在項(xiàng)目中使用到。

創(chuàng)新互聯(lián)專注于企業(yè)成都全網(wǎng)營銷推廣、網(wǎng)站重做改版、商洛網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、成都h5網(wǎng)站建設(shè)、成都商城網(wǎng)站開發(fā)、集團(tuán)公司官網(wǎng)建設(shè)、外貿(mào)網(wǎng)站制作、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為商洛等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。

言歸正傳,為什么說LongAdder引起了我的注意,原因有二:

  1. 作者是Doug lea ,地位實(shí)在舉足輕重。
  2. 他說這個(gè)比AtomicLong高效。

我們知道,AtomicLong已經(jīng)是非常好的解決方案了,涉及并發(fā)的地方都是使用CAS操作,在硬件層次上去做 compare and set操作。效率非常高。

因此,我決定研究下,為什么LongAdder比AtomicLong高效。

首先,看LongAdder的繼承樹:

繼承自Striped64,這個(gè)類包裝了一些很重要的內(nèi)部類和操作。稍候會(huì)看到。

正式開始前,強(qiáng)調(diào)下,我們知道,AtomicLong的實(shí)現(xiàn)方式是內(nèi)部有個(gè)value 變量,當(dāng)多線程并發(fā)自增,自減時(shí),均通過CAS 指令從機(jī)器指令級(jí)別操作保證并發(fā)的原子性。

再看看LongAdder的方法:


怪不得可以和AtomicLong作比較,連API都這么像。我們隨便挑一個(gè)API入手分析,這個(gè)API通了,其他API都大同小異,因此,我選擇了add這個(gè)方法。事實(shí)上,其他API也都依賴這個(gè)方法。


LongAdder中包含了一個(gè)Cell 數(shù)組,Cell是Striped64的一個(gè)內(nèi)部類,顧名思義,Cell 代表了一個(gè)最小單元,這個(gè)單元有什么用,稍候會(huì)說道。先看定義:


Cell內(nèi)部有一個(gè)非常重要的value變量,并且提供了一個(gè)CAS更新其值的方法。

回到add方法:

這里,我有個(gè)疑問,AtomicLong已經(jīng)使用CAS指令,非常高效了(比起各種鎖),LongAdder如果還是用CAS指令更新值,怎么可能比AtomicLong高效了? 何況內(nèi)部還這么多判斷?。?!

這是我開始時(shí)***的疑問,所以,我猜想,難道有比CAS指令更高效的方式出現(xiàn)了? 帶著這個(gè)疑問,繼續(xù)。

***if 判斷,***次調(diào)用的時(shí)候cells數(shù)組肯定為null,因此,進(jìn)入casBase方法:


原子更新base沒啥好說的,如果更新成功,本地調(diào)用開始返回,否則進(jìn)入分支內(nèi)部。

什么時(shí)候會(huì)更新失敗? 沒錯(cuò),并發(fā)的時(shí)候,好戲開始了,AtomicLong的處理方式是死循環(huán)嘗試更新,直到成功才返回,而LongAdder則是進(jìn)入這個(gè)分支。

分支內(nèi)部,通過一個(gè)Threadlocal變量threadHashCode 獲取一個(gè)HashCode對(duì)象,該HashCode對(duì)象依然是Striped64類的內(nèi)部類,看定義:


有個(gè)code變量,保存了一個(gè)非0的隨機(jī)數(shù)隨機(jī)值。

回到add方法:

拿到該線程相關(guān)的HashCode對(duì)象后,獲取它的code變量,as[(n-1)&h] 這句話相當(dāng)于對(duì)h取模,只不過比起取模,因?yàn)槭?與 的運(yùn)算所以效率更高。

計(jì)算出一個(gè)在Cells 數(shù)組中當(dāng)先線程的HashCode對(duì)應(yīng)的 索引位置,并將該位置的Cell 對(duì)象拿出來用CAS更新它的value值。

當(dāng)然,如果as 為null 并且更新失敗,才會(huì)進(jìn)入retryUpdate方法。

看到這里我想應(yīng)該有很多人明白為什么LongAdder會(huì)比AtomicLong更高效了,沒錯(cuò),唯一會(huì)制約AtomicLong高效的原因是高并發(fā),高并發(fā)意味著CAS的失敗幾率更高, 重試次數(shù)更多,越多線程重試,CAS失敗幾率又越高,變成惡性循環(huán),AtomicLong效率降低。 那怎么解決? LongAdder給了我們一個(gè)非常容易想到的解決方案:減少并發(fā),將單一value的更新壓力分擔(dān)到多個(gè)value中去,降低單個(gè)value的 “熱度”,分段更新?。?!

這樣,線程數(shù)再多也會(huì)分擔(dān)到多個(gè)value上去更新,只需要增加value就可以降低 value的 “熱度”  AtomicLong中的 惡性循環(huán)不就解決了嗎? cells 就是這個(gè) “段” cell中的value 就是存放更新值的, 這樣,當(dāng)我需要總數(shù)時(shí),把cells 中的value都累加一下不就可以了么??!

當(dāng)然,聰明之處遠(yuǎn)遠(yuǎn)不僅僅這里,在看看add方法中的代碼,casBase方法可不可以不要,直接分段更新,上來就計(jì)算 索引位置,然后更新value?

答案是不好,不是不行,因?yàn)椋琧asBase操作等價(jià)于AtomicLong中的CAS操作,要知道,LongAdder這樣的處理方式是有壞處的,分段操作必然帶來空間上的浪費(fèi),可以空間換時(shí)間,但是,能不換就不換,看空間時(shí)間都節(jié)約~!所以,casBase操作保證了在低并發(fā)時(shí),不會(huì)立即進(jìn)入分支做分段更新操作,因?yàn)榈筒l(fā)時(shí),casBase操作基本都會(huì)成功,只有并發(fā)高到一定程度了,才會(huì)進(jìn)入分支,所以,Doug Lea對(duì)該類的說明是: 低并發(fā)時(shí)LongAdder和AtomicLong性能差不多,高并發(fā)時(shí)LongAdder更高效!

但是,Doung Lea 還是沒這么簡單,聰明之處還沒有結(jié)束……

如此,retryUpdate中做了什么事,也基本略知一二了,因?yàn)閏ell中的value都更新失敗(說明該索引到這個(gè)cell的線程也很多,并發(fā)也很高時(shí)) 或者cells數(shù)組為空時(shí)才會(huì)調(diào)用retryUpdate,

因此,retryUpdate里面應(yīng)該會(huì)做兩件事:

  1. 擴(kuò)容,將cells數(shù)組擴(kuò)大,降低每個(gè)cell的并發(fā)量,同樣,這也意味著cells數(shù)組的rehash動(dòng)作。
  2.  給空的cells變量賦一個(gè)新的Cell數(shù)組。

是不是這樣呢? 繼續(xù)看代碼:

代碼比較長,變成文本看看,為了方便大家看if else 分支,對(duì)應(yīng)的  { } 我用相同的顏色標(biāo)注出來。可以看到,這個(gè)時(shí)候Doug Lea才愿意使用死循環(huán)保證更新成功~!

 
 
  1. final void retryUpdate(long x, HashCode hc, boolean wasUncontended) { 
  2.       int h = hc.code; 
  3.       boolean collide = false;                // True if last slot nonempty 
  4.       for (;;) { 
  5.           Cell[] as; Cell a; int n; long v; 
  6.           if ((as = cells) != null && (n = as.length) > 0) {// 分支1 
  7.               if ((a = as[(n - 1) & h]) == null) { 
  8.                   if (busy == 0) {            // Try to attach new Cell 
  9.                       Cell r = new Cell(x);   // Optimistically create 
  10.                       if (busy == 0 && casBusy()) { 
  11.                           boolean created = false; 
  12.                           try {               // Recheck under lock 
  13.                               Cell[] rs; int m, j; 
  14.                               if ((rs = cells) != null && 
  15.                                       (m = rs.length) > 0 && 
  16.                                       rs[j = (m - 1) & h] == null) { 
  17.                                   rs[j] = r; 
  18.                                   created = true; 
  19.                               } 
  20.                           } finally { 
  21.                               busy = 0; 
  22.                           } 
  23.                           if (created) 
  24.                               break; 
  25.                           continue;           // Slot is now non-empty 
  26.                       } 
  27.                   } 
  28.                   collide = false; 
  29.               } 
  30.               else if (!wasUncontended)       // CAS already known to fail 
  31.                   wasUncontended = true;      // Continue after rehash 
  32.               else if (a.cas(v = a.value, fn(v, x))) 
  33.                   break; 
  34.               else if (n >= NCPU || cells != as) 
  35.                   collide = false;            // At max size or stale 
  36.               else if (!collide) 
  37.                   collide = true; 
  38.               else if (busy == 0 && casBusy()) { 
  39.                   try { 
  40.                       if (cells == as) {      // Expand table unless stale 
  41.                           Cell[] rs = new Cell[n << 1]; 
  42.                           for (int i = 0; i < n; ++i) 
  43.                               rs[i] = as[i]; 
  44.                           cells = rs; 
  45.                       } 
  46.                   } finally { 
  47.                       busy = 0; 
  48.                   } 
  49.                   collide = false; 
  50.                   continue;                   // Retry with expanded table 
  51.               } 
  52.               h ^= h << 13;                   // Rehash  h ^= h >>> 17; 
  53.               h ^= h << 5; 
  54.           } 
  55.           else if (busy == 0 && cells == as && casBusy()) {//分支2 
  56.               boolean init = false; 
  57.               try {                           // Initialize table 
  58.                   if (cells == as) { 
  59.                       Cell[] rs = new Cell[2]; 
  60.                       rs[h & 1] = new Cell(x); 
  61.                       cells = rs; 
  62.                       init = true; 
  63.                   } 
  64.               } finally { 
  65.                   busy = 0; 
  66.               } 
  67.               if (init) 
  68.                   break; 
  69.           } 
  70.           else if (casBase(v = base, fn(v, x))) 
  71.               break;                          // Fall back on using base 
  72.       } 
  73.       hc.code = h;                            // Record index for next time 
  74.   }

分支2中,為cells為空的情況,需要new 一個(gè)Cell數(shù)組。

分支1分支中,略復(fù)雜一點(diǎn)點(diǎn):

注意,幾個(gè)分支中都提到了busy這個(gè)方法,這個(gè)可以理解為一個(gè)CAS實(shí)現(xiàn)的鎖,只有在需要更新cells數(shù)組的時(shí)候才會(huì)更新該值為1,如果更新失敗,則說明當(dāng)前有線程在更新cells數(shù)組,當(dāng)前線程需要等待。重試。

回到分支1中,這里首先判斷當(dāng)前cells數(shù)組中的索引位置的cell元素是否為空,如果為空,則添加一個(gè)cell到數(shù)組中。

否則更新 標(biāo)示沖突的標(biāo)志位wasUncontended 為 true ,重試。

否則,再次更新cell中的value,如果失敗,重試。

。。。。。。。一系列的判斷后,如果還是失敗,下下下策,reHash,直接將cells數(shù)組擴(kuò)容一倍,并更新當(dāng)前線程的hash值,保證下次更新能盡可能成功。

可以看到,LongAdder確實(shí)用了很多心思減少并發(fā)量,并且,每一步都是在”沒有更好的辦法“的時(shí)候才會(huì)選擇更大開銷的操作,從而盡可能的用最最簡單的辦法去完成操作。追求簡單,但是絕對(duì)不粗暴。

#p#

———————陳皓注————————

***留給大家思考的兩個(gè)問題:

1)是不是AtomicLong可以被廢了?

2)如果cell被創(chuàng)建后,原來的casBase就不走了,會(huì)不會(huì)性能更差?

———————liuinsect注————————

昨天和左耳朵耗子簡單討論了下,發(fā)現(xiàn)左耳朵耗子,耗哥對(duì)讀者思維的引導(dǎo)還是非常不錯(cuò)的,在***次發(fā)現(xiàn)這個(gè)類后,對(duì)里面的實(shí)現(xiàn)又提出了更多的問題,引導(dǎo)大家思考,值得學(xué)習(xí)。

我們 發(fā)現(xiàn)的問題有這么幾個(gè)(包括以上的問題),自己簡單總結(jié)下,歡迎大家討論:

1. jdk 1.7中是不是有這個(gè)類?

我確認(rèn)后,結(jié)果如下:    jdk-7u51 版本上還沒有  但是jdk-8u20版本上已經(jīng)有了。代碼基本一樣 ,增加了對(duì)double類型的支持和刪除了一些冗余的代碼。有興趣的同學(xué)可以去下載下JDK 1.8看看

2. base有沒有參與匯總?

base在調(diào)用intValue等方法的時(shí)候是會(huì)匯總的:

3. 如果cell被創(chuàng)建后,原來的casBase就不走了,會(huì)不會(huì)性能更差? base的順序可不可以調(diào)換?

    剛開始我想可不可以調(diào)換add方法中的判斷順序,比如,先做casBase的判斷? 仔細(xì)思考后認(rèn)為還是 不調(diào)換可能更好,調(diào)換后每次都要CAS一下,在高并發(fā)時(shí),失敗幾率非常高,并且是惡性循環(huán),比起一次判斷,后者的開銷明顯小很多,還沒有副作用(上一個(gè)問題,base變量在sum時(shí)base是會(huì)被統(tǒng)計(jì)的,并不會(huì)丟掉base的值)。因此,不調(diào)換可能會(huì)更好。

4. AtomicLong可不可以廢掉?

我的想法是可以廢掉了,因?yàn)?,雖然LongAdder在空間上占用略大,但是,它的性能已經(jīng)足以說明一切了,無論是從節(jié)約空的角度還是執(zhí)行效率上,AtomicLong基本沒有優(yōu)勢了,具體看這個(gè)測試(感謝Lemon的回復(fù)):http://blog.palominolabs.com/2014/02/10/java-8-performance-improvements-longadder-vs-atomiclong/

(全文完)


當(dāng)前文章:從LongAdder看更高效的無鎖實(shí)現(xiàn)
文章起源:http://www.5511xx.com/article/dhsphio.html