線程最快累加方案
學習多線程期間,想了一個問題,多個線程累計時,怎樣才能使計算又快又安全?
問題:一個初始值為0的變量,10個線程對其進行累計,一個線程對其累加 100_000_000 次,每次加2,請求結果及耗時短的方案?
四種方案,如下:
1 import java.util.concurrent.BrokenBarrierException; 2 import java.util.concurrent.CountDownLatch; 3 import java.util.concurrent.atomic.AtomicInteger; 4 import java.util.concurrent.atomic.LongAccumulator; 5 import java.util.concurrent.atomic.LongAdder; 6 7 public class FastAccumulator { 8 9 public static int THREAD_NUM = 10; 10 public static int COUNT_NUM = 100_000_000; 11 public static CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUM); 12 13 // 方案一:使用 普通遍歷 + synchronized關鍵字 14 public static Integer count = 0; 15 // 方案二:使用 AtomicInteger 並發原子類 16 // public static AtomicInteger count = new AtomicInteger(); 17 // 方案三:使用 juc包中的 LongAdder 18 // public static LongAdder count = new LongAdder(); 19 // 方案四:使用 juc包中的 LongAdder 20 // public static LongAccumulator count = new LongAccumulator((left, right) -> left + right,0); 21 22 public static void main(String[] args) throws InterruptedException, BrokenBarrierException { 23 24 long start = System.currentTimeMillis(); 25 for (int i = 0; i < THREAD_NUM; i++) 26 new Thread(() -> { 27 for (int j = 0; j < COUNT_NUM; j++) { 28 // 方案一 29 synchronized (FastAccumulator.class) { 30 count += 2; 31 } 32 // 方案二 33 // count.addAndGet(2); 34 // 方案三 35 // count.add(2); 36 // 方案四 37 // count.accumulate(2); 38 39 } 40 countDownLatch.countDown(); 41 }).start(); 42 countDownLatch.await(); 43 long end = System.currentTimeMillis(); 44 System.out.println(count); 45 System.out.println("耗時:" + (end - start)); 46 } 47 }
結果如下:
1 // 方案一 2 2000000000 3 耗時:47515 4 5 // 方案一 6 2000000000 7 耗時:46455 8 9 // 方案二 10 2000000000 11 耗時:22224 12 13 // 方案二 14 2000000000 15 耗時:20618 16 17 // 方案二 18 2000000000 19 耗時:20098 20 21 // 方案三 22 2000000000 23 耗時:5094 24 25 // 方案三 26 2000000000 27 耗時:4751 28 29 // 方案三 30 2000000000 31 耗時:4909 32 33 // 方案四 34 2000000000 35 耗時:4938 36 37 // 方案四 38 2000000000 39 耗時:4679 40 41 // 方案四 42 2000000000 43 耗時:5009
結果:
耗時由長到短:synchronized 關鍵字 > AtomicInteger 類 > LongAdder, LongAccumulator 類
可以看出使用 LongAdder 和 LongAccumulator 耗時最短。
原因:
方案一:synchronized 關鍵字,代碼運行會將鎖升級到重量級鎖,比較耗時
方案二:AtomicInteger 類,在內存中使用CAS自旋累加,但是加的結果都指向內存中的一個變量,沖突會比較嚴重,耗時較多
方案三: LongAdder 和 LongAccumulator 底層使用了 base(基本值)+ Cell[] cells(單元表)來保持數據,當需要進行累加一個值 n 時,根據線程的一個特有值計算得到對應的cells[i],執行 cells[i] = cells[i] + n,如果沖突可以對 cells 擴容,或者把值 n 累加到 base 上,這樣就有多個變量可以進行累加,最后求和是只要把 base 和 cells 數組中的值都加起來即可。(ConcurrentHashMap 中,添加元素后對集合大小累加時,也是這樣方案)
