java多線程之AtomicLong與LongAdder

AtomicLong簡要介紹

AtomicLong是做用是對長整形進行原子操做,顯而易見,在java1.8中新加入了一個新的原子類LongAdder,該類也能夠保證Long類型操做的原子性,相對於AtomicLong,LongAdder有着更高的性能和更好的表現,能夠徹底替代AtomicLong的來進行原子操做。
在32位操做系統中,64位的long 和 double 變量因爲會被JVM看成兩個分離的32位來進行操做,因此不具備原子性。而使用AtomicLong能讓long的操做保持原子型。java

 

AtomicLong的代碼很簡單,下面僅以incrementAndGet()爲例,對AtomicLong的原理進行說明。
incrementAndGet()源碼以下:數組

  1. public final long incrementAndGet() {  
  2.     for (;;) {  
  3.         // 獲取AtomicLong當前對應的long值  
  4.         long current = get();  
  5.         // 將current加1  
  6.         long next = current + 1;  
  7.         // 經過CAS函數,更新current的值  
  8.         if (compareAndSet(current, next))  
  9.             return next;  
  10.     }  
  11. }

說明
(01) incrementAndGet()首先會根據get()獲取AtomicLong對應的long值。該值是volatile類型的變量,get()的源碼以下:緩存

  1. // value是AtomicLong對應的long值  
  2. private volatile long value;  
  3. // 返回AtomicLong對應的long值  
  4. public final long get() {  
  5.     return value;  
  6. } 

(02) incrementAndGet()接着將current加1,而後經過CAS函數,將新的值賦值給value。
compareAndSet()的源碼以下:多線程

  1. public final boolean compareAndSet(long expect, long update) {  
  2.     return unsafe.compareAndSwapLong(this, valueOffset, expect, update);  
  3. } 

compareAndSet()的做用是更新AtomicLong對應的long值。它會比較AtomicLong的原始值是否與expect相等,若相等的話,則設置AtomicLong的值爲update。併發

比AtomicLong更高效的LongAdder 源碼解析

接觸到AtomicLong的緣由是在看guava的LoadingCache相關代碼時,關於LoadingCache,其實思路也很是簡單清晰:用模板模式解決了緩存不命中時獲取數據的邏輯,這個思路我早前也正好在項目中使用到。less

言歸正傳,爲何說LongAdder引發了個人注意,緣由有二:ide

1. 做者是Doug lea ,地位實在舉足輕重。函數

2. 他說這個比AtomicLong高效。高併發

咱們知道,AtomicLong已是很是好的解決方案了,涉及併發的地方都是使用CAS操做,在硬件層次上去作 compare and set操做。效率很是高。post

所以,我決定研究下,爲何LongAdder比AtomicLong高效。

首先,看LongAdder的繼承樹:

la1

繼承自Striped64,這個類包裝了一些很重要的內部類和操做。稍候會看到。

正式開始前,強調下,咱們知道,AtomicLong的實現方式是內部有個value 變量,當多線程併發自增,自減時,均經過cas 指令從機器指令級別操做保證併發的原子性。

再看看LongAdder的方法:

la2

怪不得能夠和AtomicLong做比較,連API都這麼像。咱們隨便挑一個API入手分析,這個API通了,其餘API都大同小異,所以,我選擇了add這個方法。事實上,其餘API也都依賴這個方法。

la3

LongAdder中包含了一個Cell 數組,Cell是Striped64的一個內部類,顧名思義,Cell 表明了一個最小單元,這個單元有什麼用,稍候會說道。先看定義:

la4

Cell內部有一個很是重要的value變量,而且提供了一個cas更新其值的方法。

回到add方法:

la3

這裏,我有個疑問,AtomicLong已經使用CAS指令,很是高效了(比起各類鎖),LongAdder若是仍是用CAS指令更新值,怎麼可能比AtomicLong高效了? 況且內部還這麼多判斷!!!

這是我開始時最大的疑問,因此,我猜測,難道有比CAS指令更高效的方式出現了? 帶着這個疑問,繼續。

第一if 判斷,第一次調用的時候cells數組確定爲null,所以,進入casBase方法:

la5

原子更新base沒啥好說的,若是更新成功,本地調用開始返回,不然進入分支內部。

何時會更新失敗? 沒錯,併發的時候,好戲開始了,AtomicLong的處理方式是死循環嘗試更新,直到成功才返回,而LongAdder則是進入這個分支。

分支內部,經過一個Threadlocal變量threadHashCode 獲取一個HashCode對象,該HashCode對象依然是Striped64類的內部類,看定義:

la6

有個code變量,保存了一個非0的隨機數隨機值。

回到add方法:

la3

拿到該線程相關的HashCode對象後,獲取它的code變量,as[(n-1)h] 這句話至關於對h取模,只不過比起取摸,由於是 與 的運算因此效率更高。

計算出一個在Cells 數組中當先線程的HashCode對應的 索引位置,並將該位置的Cell 對象拿出來更新cas 更新它的value值。

固然,若是as 爲null 而且更新失敗,纔會進入retryUpdate方法。

看到這裏我想應該有不少人明白爲何LongAdder會比AtomicLong更高效了,沒錯,惟一會制約AtomicLong高效的緣由是高併發,高併發意味着CAS的失敗概率更高, 重試次數更多,越多線程重試,CAS失敗概率又越高,變成惡性循環,AtomicLong效率下降。 那怎麼解決? LongAdder給了咱們一個很是容易想到的解決方案: 減小併發,將單一value的更新壓力分擔到多個value中去,下降單個value的 「熱度」,分段更新!!!   這樣,線程數再多也會分擔到多個value上去更新,只須要增長value就能夠下降 value的 「熱度」  AtomicLong中的 惡性循環不就解決了嗎? cells 就是這個 「段」 cell中的value 就是存放更新值的, 這樣,當我須要總數時,把cells 中的value都累加一下不就能夠了麼!!

固然,聰明之處遠遠不只僅這裏,在看看add方法中的代碼,casBase方法可不能夠不要,直接分段更新,上來就計算 索引位置,而後更新value?

答案是很差,不是不行,由於,casBase操做等價於AtomicLong中的cas操做,要知道,LongAdder這樣的處理方式是有壞處的,分段操做必然帶來空間上的浪費,能夠空間換時間,可是,能不換就不換,看空間時間都節約~! 因此,casBase操做保證了在低併發時,不會當即進入分支作分段更新操做,由於低併發時,casBase操做基本都會成功,只有併發高到必定程度了,纔會進入分支,因此,Doug Lead對該類的說明是: 低併發時LongAdder和AtomicLong性能差很少,高併發時LongAdder更高效!

la7

可是,Doung Lea 仍是沒這麼簡單,聰明之處尚未結束……

如此,retryUpdate中作了什麼事,也基本略知一二了,由於cell中的value都更新失敗(說明該索引到這個cell的線程也不少,併發也很高時) 或者cells數組爲空時纔會調用retryUpdate,

所以,retryUpdate裏面應該會作兩件事:

1. 擴容,將cells數組擴大,下降每一個cell的併發量,一樣,這也意味着cells數組的rehash動做。

2. 給空的cells變量賦一個新的Cell數組。

是否是這樣呢? 繼續看代碼:

代碼比較長,變成文本看看,爲了方便你們看if else 分支,對應的  { } 我用相同的顏色標註出來

能夠看到,這個時候Doug Lea才願意使用死循環保證更新成功~!

 
inal void retryUpdate(long x, HashCode hc, boolean wasUncontended) {
    int h = hc.code;
    boolean collide = false;                // True if last slot nonempty
    for (;;) {
        Cell[] as; Cell a; int n; long v;
        if ((as = cells) != null && (n = as.length) > 0) {// 分支1
            if ((a = as[(n - 1) & h]) == null) {
                if (busy == 0) {            // Try to attach new Cell
                    Cell r = new Cell(x);   // Optimistically create
                    if (busy == 0 && casBusy()) {
                        boolean created = false;
                        try {               // Recheck under lock
                            Cell[] rs; int m, j;
                            if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                rs[j] = r;
                                created = true;
                            }
                        } finally {
                            busy = 0;
                        }
                        if (created)
                            break;
                        continue;           // Slot is now non-empty
                    }
                }
                collide = false;
            }
            else if (!wasUncontended)       // CAS already known to fail
                wasUncontended = true;      // Continue after rehash
            else if (a.cas(v = a.value, fn(v, x)))
                break;
            else if (n >= NCPU || cells != as)
                collide = false;            // At max size or stale
            else if (!collide)
                collide = true;
            else if (busy == 0 && casBusy()) {
                try {
                    if (cells == as) {      // Expand table unless stale
                        Cell[] rs = new Cell[n << 1];
                        for (int i = 0; i < n; ++i)
                            rs[i] = as[i];
                        cells = rs;
                    }
                } finally {
                    busy = 0;
                }
                collide = false;
                continue;                   // Retry with expanded table
            }
            h ^= h << 13;                   // Rehash                 h ^= h >>> 17;
            h ^= h << 5;
        }
        else if (busy == 0 && cells == as && casBusy()) {//分支2
            boolean init = false;
            try {                           // Initialize table
                if (cells == as) {
                    Cell[] rs = new Cell[2];
                    rs[h & 1] = new Cell(x);
                    cells = rs;
                    init = true;
                }
            } finally {
                busy = 0;
            }
            if (init)
                break;
        }
        else if (casBase(v = base, fn(v, x)))
            break;                          // Fall back on using base
    }
    hc.code = h;                            // Record index for next time
}

 

分支2中,爲cells爲空的狀況,須要new 一個Cell數組。

分支1分支中,略複雜一點點:

注意,幾個分支中都提到了busy這個方法,這個能夠理解爲一個cas實現的鎖,只有在須要更新cells數組的時候纔會更新該值爲1,若是更新失敗,則說明當前有線程在更新cells數組,當前線程須要等待。重試。

回到分支1中,這裏首先判斷當前cells數組中的索引位置的cell元素是否爲空,若是爲空,則添加一個cell到數組中。

不然更新 標示衝突的標誌位wasUncontended 爲 true ,重試。

不然,再次更新cell中的value,若是失敗,重試。

。。。。。。。一系列的判斷後,若是仍是失敗,下下下策,reHash,直接將cells數組擴容一倍,並更新當前線程的hash值,保證下次更新能儘量成功。

能夠看到,LongAdder確實用了不少心思減小併發量,而且,每一步都是在」沒有更好的辦法「的時候纔會選擇更大開銷的操做,從而儘量的用最最簡單的辦法去完成操做。追求簡單,可是絕對不粗暴。

————————————————分割線——————————————————————-

昨天和左耳朵耗子簡單討論了下,發現左耳朵耗子對讀者思惟的引導仍是很是不錯的,在第一次發現這個類後,對裏面的實現又提出了更多的問題,引導你們思考,值得學習。

咱們 發現的問題有這麼幾個:

1. jdk 1.7中是否是有這個類?
我確認後,結果以下:    jdk-7u51 版本上尚未  可是jdk-8u20版本上已經有了。代碼基本同樣 ,增長了對double類型的支持和刪除了一些冗餘的代碼。有興趣的同窗能夠去下載下JDK 1.8看看

2. base有沒有參與彙總?
base在調用intValue等方法的時候是會彙總的:

LA10
3. base的順序可不能夠調換?
左耳朵耗子,提出了這麼一個問題: 在add方法中,若是cells不會爲空後,casBase方法一直都沒有用了?

所以,我想可不能夠調換add方法中的判斷順序,好比,先作casBase的判斷,結果是 不調換可能更好,調換後每次都要CAS一下,在高併發時,失敗概率很是高,而且是惡性循環,比起一次判斷,後者的開銷明顯小不少,尚未反作用。所以,不調換可能會更好。

4. AtomicLong可不能夠廢掉?
個人想法是能夠廢掉了,由於,雖然LongAdder在空間上佔用略大,可是,它的性能已經足以說明一切了,不管是從節約空的角度仍是執行效率上,AtomicLong基本沒有優點了,具體看這個測試 :http://blog.palominolabs.com/2014/02/10/java-8-performance-improvements-longadder-vs-atomiclong/

 

轉自:http://blog.csdn.net/wangxiaotongfan/article/details/51745506?locationNum=1&fps=1

相關文章
相關標籤/搜索