线程安装的List:Collections.synchronizedList与CopyOnWriteArrayList比较

avatar 2019年8月20日19:27:59 评论 61 views

 

之前帮一个朋友写代码的时候,遇到一个需要遍历列表。

 

一、单线程:性能较差

版本一、单线程方式

  1. Long startTime = System.currentTimeMillis();
  2.        //这是一个长度为1000的集合
  3.        List<Long> sourceList = new ArrayList<>();
  4.        for (long i = 0L; i < 1000L; i++) {
  5.            sourceList.add(i);
  6.        }
  7.        System.out.println("原列表大小:" + sourceList.size());
  8.        //对原列表进行处理
  9.        List<Long> resultList = new ArrayList<>();
  10.        for (Long x : sourceList) {
  11.            //模拟耗时操作(x累加300万次)
  12.            Long sum = 0L;
  13.            for (long i = 0L; i < 3000000L; i++) {
  14.                sum += x;
  15.            }
  16.            resultList.add(sum);
  17.        }
  18.        System.out.println("处理后的列表大小:" + resultList.size());
  19.        System.out.println("耗时:" + (System.currentTimeMillis() - startTime) + "ms");

输出结果如下

 

二、多线程版本,不安全的 ArrayList

  1. Long startTime = System.currentTimeMillis();
  2.        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
  3.        //这是一个长度为1000的集合
  4.        List<Long> sourceList = new ArrayList<>();
  5.        for (long i = 0L; i < 1000L; i++) {
  6.            sourceList.add(i);
  7.        }
  8.        System.out.println("原列表大小:" + sourceList.size());
  9.        List<Long> resultList = new ArrayList<>();
  10.        for (Long x : sourceList) {
  11.            fixedThreadPool.execute(new Runnable() {
  12.                @Override
  13.                public void run() {
  14.                    //对原列表进行处理
  15.                    //模拟耗时操作(x累加300万次)
  16.                    Long sum = 0L;
  17.                    for (long i = 0L; i < 3000000L; i++) {
  18.                        sum += x;
  19.                    }
  20.                    resultList.add(sum);
  21.                }
  22.            });
  23.        }
  24.        fixedThreadPool.shutdown();//关闭线程池
  25.        //此处不可以删除或注释,需要线程执行结束后再执行别的内容,即只有线程结束后才会继续向下执行
  26.        while (!fixedThreadPool.isTerminated()) {
  27.        }
  28.        System.out.println("处理后的列表大小:" + resultList.size());
  29.        System.out.println("耗时:" + (System.currentTimeMillis() - startTime) + "ms");

输出结果如下,速度提升了一半,随着处理越耗时,提升越明显

 

这里线程不安全主要原因是 resultList 这个对象的问题,ArrayList 是线程不安全的,主要原因是 add 的时候两个线程对同一个位置进行赋值,导致其中一个被覆盖了,也就丢失了。

 

线程安全的 List 有哪些呢?

synchronizedList()&CopyOnWriteArrayList

下面分别进行测试性能

 

三、多线程版本,线程安全,CopyOnWriteArrayList()方式

将版本二第11行的

List<Long> resultList = new ArrayList<>();

替换为

List<Long> resultList = new CopyOnWriteArrayList<>();

多次尝试,处理前和处理后的都是1000,CopyOnWriteArrayList() 是线程安全的

 

四、多线程版本,线程安全,Collections.synchronizedList方式

将版本二第11行的

List<Long> resultList = new ArrayList<>();

替换为

List<Long> resultList = Collections.synchronizedList(new ArrayList<>());

多次尝试,处理前和处理后的都是1000,Collections.synchronizedList 也是线程安全的

 

五、修改任务执行时间比较两种方式的性能

1.尝试通过修改任务时长比较多个List的性能

我们尝试修改版本二 中 17-23行的代码,如下

  1. //对原列表进行处理  
  2. //模拟耗时操作(x累加300万次)  
  3. Long sum = 0L;
  4. for (long i = 0L; i < 3000000L; i++) {
  5.     sum += x;
  6. }
  7. resultList.add(sum);

 

尝试修改 3000000L

横坐标表示上面for循环次数  (其他的以版本二中的例子,其他不变)

时间单位:ms 100万 200万 300万 400万  500万  1000万  2000万
ArrayList 2374 4275 6081 8368 10224 19493 39275
CopyOnWriteArrayList  2428  4429 6067 8108  10233  19421 37474
Collections.synchronizedList 2284 4394 6267  8280 9888 19314 39229

比较了一番,可能例子不太恰当,导致结果发现这三个性能差不多

 

 

但是在我印象中,包括之前帮朋友写的代码中, Collections.synchronizedList 是优于 CopyOnWriteArrayList  的。

 

CopyOnWriteArrayList 的 add 方法性能其实是不太好的

 

2.尝试修改 原始List 的个数

  1. List<Long> sourceList = new ArrayList<>();
  2.   for (long i = 0L; i < 1000L; i++) {
  3.       sourceList.add(i);
  4.   }
  5.   System.out.println("原列表大小:" + sourceList.size());

即修改1000L

横坐标表示列表大小 (其他的以版本二中的例子,其他不变)

时间单位:ms 100 200 400 800  1600  3200 6400
ArrayList 1017 1534 2891 5136 9821 18503 36601
CopyOnWriteArrayList 982 1475 2752 4930 9628 18329 36175
Collections.synchronizedList 978 1667 2727 4929 9595 18709 37673

这里也看不出来三者性能的差别

 

3.尝试修改线程数

即修改

  1. ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

博主电脑是4核16GB的

横坐标是线程数 (其他的以版本二中的例子,其他不变)

时间单位:ms 1 2 4 8  16  32 64
ArrayList 10184 6891 6715 6725 6659 6479 6622
CopyOnWriteArrayList 10111 6921 6299 6085 6651 6434 5955
Collections.synchronizedList 9893 6570 6348 6227 6359 6206 6561

目前只能看出多线程比单线程快很多的,随着线程数增多会占用更多CPU,线程上下文切换也更加频繁。但是数据量太少,没有很好的体现。

本次只是介绍了有两种线程安全的 List,但是这两者性能还没比较出来,下次有机会再换一些测试用例比较吧。

 

  • 微信
  • 交流学习,有偿服务
  • weinxin
  • 博客/Java交流群
  • 资源分享,问题解决,技术交流。群号:590480292
  • weinxin
avatar

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: