H__D  

线程最快累加方案

  学习多线程期间,想了一个问题,多个线程累计时,怎样才能使计算又快又安全?

  问题:一个初始值为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 中,添加元素后对集合大小累加时,也是这样方案)

 

posted on 2021-03-30 01:23  H__D  阅读(1018)  评论(0编辑  收藏  举报