Java並發編程之LongAdder和LongAccumulator源碼探究

來源:互聯網
上載者:User

標籤:alt   資源   OLE   rri   rem   inf   cep   compare   nsa   

一.LongAdder原理

LongAdder類是JDK1.8新增的一個原子性操作類。AtomicLong通過CAS演算法提供了非阻塞的原子性操作,相比受用阻塞演算法的同步器來說效能已經很好了,但是JDK開發組並不滿足於此,因為非常搞並發的請求下AtomicLong的效能是不能讓人接受的。

如下AtomicLong 的incrementAndGet的代碼,雖然AtomicLong使用CAS演算法,但是CAS失敗後還是通過無限迴圈的自旋鎖不多的嘗試,這就是高並發下CAS效能低下的原因所在。源碼如下:

  public final long incrementAndGet() {        for (;;) {            long current = get();            long next = current + 1;            if (compareAndSet(current, next))                return next;        }    }

在高並發下N多線程同時去操作一個變數會造成大量線程CAS失敗,然後處於自旋狀態,這樣導致大大浪費CPU資源,降低了並發性。

既然AtomicLong效能問題是由於過多線程同時去競爭同一個變數的更新而降低的,那麼如果把一個變數分解為多個變數,讓同樣多的線程去競爭多個資源,那麼效能問題不久迎刃而解了嗎?

沒錯,因此,JDK8 提供的LongAdder就是這個思路。下面通過圖形來標示兩者的不同,如:

如 AtomicLong 是多個線程同時競爭同一個變數情景。

 

如所示,LongAdder則是內部維護多個Cell變數,每個Cell裡面有一個初始值為0的long型變數,在同等並發量的情況下,爭奪單個變數的線程會減少,這是變相的減少了爭奪共用資源的並發量,另外多個線程在爭奪同一個原子變數時候,

如果失敗並不是自旋CAS重試,而是嘗試擷取其他原子變數的鎖,最後當擷取當前值時候是把所有變數的值累加後再加上base的值返回的。

 

LongAdder維護了要給延遲初始化的原子性更新數組和一個基值變數base數組的大小保持是2的N次方大小,數組表的下標使用每個線程的hashcode值的掩碼錶示,數組裡面的變數實體是Cell類型。

Cell 類型是Atomic的一個改進,用來減少緩衝的爭用,對於大多數原子操作位元組填充是浪費的,因為原子操作都是無規律的分散在記憶體中進行的,多個原子性操作彼此之間是沒有接觸的,但是原子性數組元素彼此相鄰存放將能經常共用快取行,也就是偽共用。所以這在效能上是一個提升。

另外由於Cells佔用記憶體是相對比較大的,所以一開始並不建立,而是在需要時候再建立,也就是惰性載入,當一開始沒有空間時候,所有的更新都是操作base變數。

 

接下來進行LongAdder代碼簡單分析

這裡我只是簡單的介紹一下代碼的實現,詳細實現,大家可以翻看代碼去研究。為了降低高並發下多線程對一個變數CAS爭奪失敗後大量線程會自旋而造成降低並發效能問題,LongAdder內部通過根據並發請求量來維護多個Cell元素(一個動態Cell數組)來分擔對單個變數進行爭奪資源。

首先我們先看LongAdder的構造類圖,如:

 

 可以看到LongAdder繼承自Striped64類,Striped64內部維護著三個變數,LongAdder的真實值其實就是base的值與Cell數組裡面所有Cell元素值的累加,base是個基礎值,預設是0,cellBusy用來實現自旋鎖,當建立Cell元素或者擴容Cell數組時候用來進行線程間的同步。

 

接下來進去源碼如看Cell的構造,源碼如下:

  @sun.misc.Contended static final class Cell {        volatile long value;        Cell(long x) { value = x; }        final boolean cas(long cmp, long val) {            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);        }        // Unsafe 技術        private static final sun.misc.Unsafe UNSAFE;        private static final long valueOffset;        static {            try {                UNSAFE = sun.misc.Unsafe.getUnsafe();                Class<?> ak = Cell.class;                valueOffset = UNSAFE.objectFieldOffset                    (ak.getDeclaredField("value"));            } catch (Exception e) {                throw new Error(e);            }        }    }

正如上面的代碼可以知道Cell的構造很簡單,內部維護一個聲明volatile的變數,這裡聲明為volatile是因為線程操作value變數時候沒有使用鎖,為了保證變數的記憶體可見度這裡只有聲明為volatile。另外這裡就是先前檔案所說的使用Unsafe類的方法來設定value的值

 

接下來進入LongAdder的源碼裡面去看幾個重要的方法,如下:

  1.long sum() 方法:返回當前的值,內部操作是累加所有 Cell 內部的 value 的值後累加 base,如下代碼,由於計算總和時候沒有對 Cell 數組進行加鎖,所以在累加過程中可能有其它線程對 Cell 中的值進行了修改,也有可能數組進行了擴容,所以 sum 返回的值並不是非常精確的,

傳回值並不是一個調用 sum 方法時候的一個原子快照值。

  源碼如下:

  

public long sum() {        Cell[] as = cells; Cell a;        long sum = base;        if (as != null) {            for (int i = 0; i < as.length; ++i) {                if ((a = as[i]) != null)                    sum += a.value;            }        }        return sum;}

 

  2.void reset() 方法:重設操作,如下代碼把 base 置為 0,如果 Cell 數組有元素,則元素值重設為 0。源碼如下:

  

  public void reset() {        Cell[] as = cells; Cell a;        base = 0L;        if (as != null) {            for (int i = 0; i < as.length; ++i) {                if ((a = as[i]) != null)                    a.value = 0L;            }        }   }

 

  3.long sumThenReset() 方法:是sum 的改造版本,如下代碼,在計算 sum 累加對應的 cell 值後,把當前 cell 的值重設為 0,base 重設為 0。 當多線程調用該方法時候會有問題,比如考慮第一個調用線程會清空 Cell 的值,後一個線程調用時候累加時候累加的都是 0 值。

  源碼如下:

  

  public long sumThenReset() {        Cell[] as = cells; Cell a;        long sum = base;        base = 0L;        if (as != null) {            for (int i = 0; i < as.length; ++i) {                if ((a = as[i]) != null) {                    sum += a.value;                    a.value = 0L;                }            }        }        return sum;    }

 

  4.long longValue() 等價於 sum(),源碼如下:

  

    public long longValue() {        return this.sum();    }

 

  5.void add(long x) 累加增量 x 到原子變數,這個過程是原子性的。源碼如下:

  

  public void add(long x) {        Cell[] as; long b, v; int m; Cell a;        if ((as = cells) != null || !casBase(b = base, b + x)) {//(1)            boolean uncontended = true;            if (as == null || (m = as.length - 1) < 0 ||//(2)                (a = as[getProbe() & m]) == null ||//(3)                !(uncontended = a.cas(v = a.value, v + x)))//(4)                longAccumulate(x, null, uncontended);//(5)        }    }    final boolean casBase(long cmp, long val) {        return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);    }

可以看到上面代碼,當第一個線程A執行add時候,代碼(1)會執行casBase方法,通過CAS設定base為 X, 如果成功則直接返回,這時候base的值為1。

假如多個線程同時執行add時候,同時執行到casBase則只有一個線程A成功返回,其他線程由於CAS失敗執行代碼(2),代碼(2)是擷取cells數組的長度,如果數組長度為0,則執行代碼(5),否則cells長度不為0,說明cells數組有元素則執行代碼(3),

代碼(3)首先計算當前線程在數組中下標,然後擷取當前線程對應的cell值,如果擷取到則執行(4)進行CAS操作,CAS失敗則執行代碼(5)。

代碼(5)裡面是具體進行數組擴充和初始化,這個代碼比較複雜,這裡就不講解了,有興趣的可以進去看看。

 

二.LongAccumulator類源碼分析

LongAdder類是LongAccumulator的一個特例,LongAccumulator提供了比LongAdder更強大的功能,如下建構函式,其中accumulatorFunction是一個雙目運算器介面,根據輸入的兩個參數返回一個計算值,identity則是LongAccumulator累加器的初始值。

public LongAccumulator(LongBinaryOperator accumulatorFunction,long identity) {        this.function = accumulatorFunction;        base = this.identity = identity;}public interface LongBinaryOperator {       //根據兩個參數計算返回一個值       long applyAsLong(long left, long right);}

上面提到LongAdder 其實就是LongAccumulator 的一個特例,調用LongAdder 相當使用下面的方式調用 LongAccumulator。

   LongAdder adder = new LongAdder();    LongAccumulator accumulator = new LongAccumulator(new LongBinaryOperator() {        @Override        public long applyAsLong(long left, long right) {            return left + right;        }    }, 0);

LongAccumulator相比LongAdder 可以提供累加器初始非0值,後者只能預設為0,另外前者還可以指定累加規則,比如不是累加而相乘,只需要構造LongAccumulator 時候傳入自訂雙目運算器即可,後者則內建累加規則。

 

從下面代碼知道LongAccumulator相比於LongAdde的不同在於casBase的時候,後者傳遞的是b+x,而前者則是調用了r=function.applyAsLong(b=base.x)來計算。

LongAdder類的add源碼如下:

  public void add(long x) {        Cell[] as; long b, v; int m; Cell a;        if ((as = cells) != null || !casBase(b = base, b + x)) {            boolean uncontended = true;            if (as == null || (m = as.length - 1) < 0 ||                (a = as[getProbe() & m]) == null ||                !(uncontended = a.cas(v = a.value, v + x)))                longAccumulate(x, null, uncontended);        }    }

 

LongAccumulator的accumulate方法的源碼如下:

  public void accumulate(long x) {        Cell[] as; long b, v, r; int m; Cell a;        if ((as = cells) != null ||            (r = function.applyAsLong(b = base, x)) != b && !casBase(b, r)) {            boolean uncontended = true;            if (as == null || (m = as.length - 1) < 0 ||                (a = as[getProbe() & m]) == null ||                !(uncontended =                  (r = function.applyAsLong(v = a.value, x)) == v ||                  a.cas(v, r)))                longAccumulate(x, function, uncontended);        }    }

 

另外LongAccumulator調用longAccumulate時候傳遞的是function,而LongAdder是null,從下面代碼可以知道當fn為null,時候就是使用v+x  加法運算,這時候就等價於LongAdder,fn不為null的時候則使用傳遞的fnFunction Compute,如果fn為加法則等價於LongAdder;

else if (casBase(v = base, ((fn == null) ? v + x :fn.applyAsLong(v, x))))       // Fall back on using base
      break;
 

 

Java並發編程之LongAdder和LongAccumulator源碼探究

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.