一.LongAdder是什么
JDK1.8時,java.util.concurrent.atomic
包中提供了一個新的原子類:LongAdder
。提供了原子累計值的方法。
根據Oracle官方文檔的介紹,LongAdder在高並發的場景下會比它的前輩————AtomicLong 具有更好的性能,代價是消耗更多的內存空間:
二.LongAdder能做什么
在並發量較低的環境下,線程沖突的概率比較小,自旋的次數不會很多。但是,高並發環境下,N個線程同時進行自旋操作,會出現大量失敗並不斷自旋的情況,此時AtomicLong的自旋會成為瓶頸。
這就是LongAdder引入的初衷——解決高並發環境下AtomicLong的自旋瓶頸問題。
在大數據處理過程,為了方便監控,需要統計數據,少不了原子計數器。為了盡量優化性能,需要采用高效的原子計數器。
在jdk8中,引入了LongAddr,非常適合多線程原子計數器。與AtomicLong做了一個測試,LongAdder在多線程環境中,原子自增長性能要好很多。它常用於狀態采集、統計等場景。
三.LongAdder原理
LongAdder的原理是,在最初無競爭時,只更新base的值,當有多線程競爭時通過分段的思想,讓不同的線程更新不同的段,最后把這些段相加就得到了完整的LongAdder存儲的值。
AtomicLong的如圖:
LongAdder的如圖:
AtomicLong中有個內部變量value保存着實際的long值,所有的操作都是針對該變量進行。也就是說,高並發環境下,value變量其實是一個熱點,也就是N個線程競爭一個熱點。
LongAdder的基本思路就是分散熱點,將value值分散到一個數組中,不同線程會命中到數組的不同槽中,各個線程只對自己槽中的那個值進行CAS操作,
這樣熱點就被分散了,沖突的概率就小很多。如果要獲取真正的long值,只要將各個槽中的變量值累加返回。
AtomicLong是多個線程針對單個熱點值value進行原子操作。而LongAdder是每個線程擁有自己的槽,各個線程一般只對自己槽中的那個值進行CAS操作。
LongAdder繼承自Striped64抽象類,Striped64中定義了Cell內部類和各重要屬性。
唯一會制約AtomicLong高效的原因是高並發,高並發意味着CAS的失敗幾率更高, 重試次數更多,越多線程重試,CAS失敗幾率又越高,變成惡性循環,AtomicLong效率降低。
那怎么解決? LongAdder給了一個非常容易想到的解決方案:減少並發,將單一value的更新壓力分擔到多個value中去,降低單個value的 “熱度”,分段更新。
這樣,線程數再多也會分擔到多個value上去更新,只需要增加value就可以降低 value的 “熱度” ,AtomicLong中的 惡性循環就解決了。
cells 就是這個 “段” cell中的value 就是存放更新值的, 這樣,當需要總數時,把cells 中的value都累加一下就可以了。
四.LongAdder使用
LongAdder VS AtomicLong:
代碼如下
public class LongAdderVSAtomicLongTest {
public static void main(String[] args){ testAtomicLongVSLongAdder(1, 10000000); testAtomicLongVSLongAdder(10, 10000000); testAtomicLongVSLongAdder(20, 10000000); testAtomicLongVSLongAdder(40, 10000000); testAtomicLongVSLongAdder(80, 10000000); } static void testAtomicLongVSLongAdder(final int threadCount, final int times){ try { System.out.println("threadCount:" + threadCount + ", times:" + times); long start = System.currentTimeMillis(); testLongAdder(threadCount, times); System.out.println("LongAdder elapse:" + (System.currentTimeMillis() - start) + "ms"); long start2 = System.currentTimeMillis(); testAtomicLong(threadCount, times); System.out.println("AtomicLong elapse:" + (System.currentTimeMillis() - start2) + "ms"); } catch (InterruptedException e) { e.printStackTrace(); } } static void testAtomicLong(final int threadCount, final int times) throws InterruptedException { AtomicLong atomicLong = new AtomicLong(); List<Thread> list = new ArrayList<>(); for (int i=0;i<threadCount;i++){ list.add(new Thread(() -> { for (int j = 0; j<times; j++){ atomicLong.incrementAndGet(); } })); } for (Thread thread : list){ thread.start(); } for (Thread thread : list){ thread.join(); } } static void testLongAdder(final int threadCount, final int times) throws InterruptedException { LongAdder longAdder = new LongAdder(); List<Thread> list = new ArrayList<>(); for (int i=0;i<threadCount;i++){ list.add(new Thread(() -> { for (int j = 0; j<times; j++){ longAdder.add(1); } })); } for (Thread thread : list){ thread.start(); } for (Thread thread : list){ thread.join(); } } }
運行結果如下:
threadCount:1, times:10000000 LongAdder elapse:158ms AtomicLong elapse:64ms threadCount:10, times:10000000 LongAdder elapse:206ms AtomicLong elapse:2449ms threadCount:20, times:10000000 LongAdder elapse:429ms AtomicLong elapse:5142ms threadCount:40, times:10000000 LongAdder elapse:840ms AtomicLong elapse:10506ms threadCount:80, times:10000000 LongAdder elapse:1369ms AtomicLong elapse:20482ms
可以看到當只有一個線程的時候,AtomicLong反而性能更高,隨着線程越來越多,AtomicLong的性能急劇下降,而LongAdder的性能影響很小。
總結:
(1)LongAdder通過base和cells數組來存儲值;
(2)不同的線程會hash到不同的cell上去更新,減少了競爭;
(3)LongAdder的性能非常高,最終會達到一種無競爭的狀態;