线程最快累加方案
学习多线程期间,想了一个问题,多个线程累计时,怎样才能使计算又快又安全?
问题:一个初始值为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 中,添加元素后对集合大小累加时,也是这样方案)