之前帮一个朋友写代码的时候,遇到一个需要遍历列表。
一、单线程:性能较差
版本一、单线程方式
- Long startTime = System.currentTimeMillis();
- //这是一个长度为1000的集合
- List<Long> sourceList = new ArrayList<>();
- for (long i = 0L; i < 1000L; i++) {
- sourceList.add(i);
- }
- System.out.println("原列表大小:" + sourceList.size());
- //对原列表进行处理
- List<Long> resultList = new ArrayList<>();
- for (Long x : sourceList) {
- //模拟耗时操作(x累加300万次)
- Long sum = 0L;
- for (long i = 0L; i < 3000000L; i++) {
- sum += x;
- }
- resultList.add(sum);
- }
- System.out.println("处理后的列表大小:" + resultList.size());
- System.out.println("耗时:" + (System.currentTimeMillis() - startTime) + "ms");
输出结果如下
二、多线程版本,不安全的 ArrayList
- Long startTime = System.currentTimeMillis();
- ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
- //这是一个长度为1000的集合
- List<Long> sourceList = new ArrayList<>();
- for (long i = 0L; i < 1000L; i++) {
- sourceList.add(i);
- }
- System.out.println("原列表大小:" + sourceList.size());
- List<Long> resultList = new ArrayList<>();
- for (Long x : sourceList) {
- fixedThreadPool.execute(new Runnable() {
- @Override
- public void run() {
- //对原列表进行处理
- //模拟耗时操作(x累加300万次)
- Long sum = 0L;
- for (long i = 0L; i < 3000000L; i++) {
- sum += x;
- }
- resultList.add(sum);
- }
- });
- }
- fixedThreadPool.shutdown();//关闭线程池
- //此处不可以删除或注释,需要线程执行结束后再执行别的内容,即只有线程结束后才会继续向下执行
- while (!fixedThreadPool.isTerminated()) {
- }
- System.out.println("处理后的列表大小:" + resultList.size());
- 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行的代码,如下
- //对原列表进行处理
- //模拟耗时操作(x累加300万次)
- Long sum = 0L;
- for (long i = 0L; i < 3000000L; i++) {
- sum += x;
- }
- 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 的个数
- List<Long> sourceList = new ArrayList<>();
- for (long i = 0L; i < 1000L; i++) {
- sourceList.add(i);
- }
- 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.尝试修改线程数
即修改
- 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,但是这两者性能还没比较出来,下次有机会再换一些测试用例比较吧。
您可以选择一种方式赞助本站
支付宝扫一扫赞助
微信钱包扫描赞助
赏