不要在线上滥用CopyOnWriteArrayList,姿势不对性能真的很糟糕

JDK为我们提供了很多用于并发场景的工具类,但是仍需要我们仔细了解每一种工具的使用场景,在不合适的场景使用不合适的工具,会导致性能更差。

JDK为我们提供了很多用于并发场景的工具类,但是仍需要我们仔细了解每一种工具的使用场景,在不合适的场景使用不合适的工具,会导致性能更差。

不要在线上滥用CopyOnWriteArrayList,姿势不对性能真的很糟糕

从JDK1.5版本,JAVA提供了线程安全的List增强版CopyOnWriteArrayList,其保持线程安全的方式是:每次修改数据时,不会直接修改数据,而是把数据复制出来一份,对复制出来的数组进行操作。

通过这样的机制,可以极大程度的提升读的并发性能,所以对于CopyOnWriteArrayList来说,非常适合读多写少或者无锁的场景。

但是,如果我们为了炫技而不分场合滥用CopyOnWriteArrayList的话,可能会带来适得其反的结果。

下面,我们通过一段测试代码,比较一下CopyOnWriteArrayList和普通加锁ArrayList的读写性能差距。

我们首先测试一下写性能的差距:构建一个CopyOnWriteArrayList和synchronizedList,通过多线程并发写入100000个元素。

List<Integer> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
//构建一个加锁的List
List<Integer> synchronizedList = Collections.synchronizedList(new ArrayList<>());
StopWatch stopWatch = new StopWatch();
int loopCount = 100000;
stopWatch.start("测试写性能:copyOnWriteArrayList");
//多线程写入100000个数字
IntStream.rangeClosed(1, loopCount).parallel()
.forEach(x -> copyOnWriteArrayList.add(ThreadLocalRandom.current().nextInt(loopCount))
);
stopWatch.stop();
stopWatch.start("测试写性能:synchronizedList");
//多线程写入100000个数字
IntStream.rangeClosed(1, loopCount).parallel()
.forEach(x -> synchronizedList.add(ThreadLocalRandom.current().nextInt(loopCount))
);
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
Map<String, Integer> result = new HashMap<>();
result.put("copyOnWriteArrayList", copyOnWriteArrayList.size());
result.put("synchronizedList", synchronizedList.size());
System.out.println(JSON.toJSONString(result));

可以清楚的看到,在大量写的情况下,CopyOnWriteArrayList的性能是远远不如普通的加锁List的,性能差距可能在100倍以上。

不要在线上滥用CopyOnWriteArrayList,姿势不对性能真的很糟糕

而之所以CopyOnWriteArrayList的写入这么慢,就是因为CopyOnWriteArrayList每次写入都要对存放元素的旧数组进行复制创建一个新数组,从而导致内存申请释放消耗很大。

不要在线上滥用CopyOnWriteArrayList,姿势不对性能真的很糟糕

我们再测试一下大量读的性能差距:先对两个List写入100000个元素,再通过多线程的方式随机get元素。

List<Integer> synchronizedList = Collections.synchronizedList(new ArrayList<>());
synchronizedList.addAll(IntStream.rangeClosed(1, 100000).boxed().collect(Collectors.toList()));

List<Integer> copyOnWriteArrayList = IntStream.rangeClosed(1, 100000).boxed()
.collect(Collectors.toCollection(CopyOnWriteArrayList::new));

StopWatch stopWatch = new StopWatch();
int loopCount = 1000000;
int count = copyOnWriteArrayList.size();
stopWatch.start("测试读性能:copyOnWriteArrayList");
IntStream.rangeClosed(1, loopCount).parallel().forEach(
x -> copyOnWriteArrayList.get(ThreadLocalRandom.current().nextInt(count))
);
stopWatch.stop();
stopWatch.start("测试读性能:synchronizedList");
IntStream.range(0, loopCount).parallel().forEach(
x -> synchronizedList.get(ThreadLocalRandom.current().nextInt(count))
);
stopWatch.stop();
System.out.println(stopWatch.prettyPrint());
Map<String,Integer> result = new HashMap<>();
result.put("copyOnWriteArrayList", copyOnWriteArrayList.size());
result.put("synchronizedList", synchronizedList.size());
System.out.println(JSON.toJSONString(result));

经过多次测试,CopyOnWriteArrayList的读性能大概在普通加锁List的2-5倍左右。

不要在线上滥用CopyOnWriteArrayList,姿势不对性能真的很糟糕

而CopyOnWriteArrayList的读之所以快,是因为CopyOnWriteArrayList读取元素是无锁状态下直接按数组下标获取。

不要在线上滥用CopyOnWriteArrayList,姿势不对性能真的很糟糕

一般来说,CopyOnWriteArrayList只适用于大量读的场景,如果有了大量写操作,性能反而不如普通的List。

JDK为我们提供了很多用于并发场景的工具类,但是仍需要我们仔细了解每一种工具的使用场景,在不合适的场景使用不合适的工具,会导致性能更差。

©本文为清一色官方代发,观点仅代表作者本人,与清一色无关。清一色对文中陈述、观点判断保持中立,不对所包含内容的准确性、可靠性或完整性提供任何明示或暗示的保证。本文不作为投资理财建议,请读者仅作参考,并请自行承担全部责任。文中部分文字/图片/视频/音频等来源于网络,如侵犯到著作权人的权利,请与我们联系(微信/QQ:1074760229)。转载请注明出处:清一色财经

(0)
打赏 微信扫码打赏 微信扫码打赏 支付宝扫码打赏 支付宝扫码打赏
清一色的头像清一色管理团队
上一篇 2023年5月6日 19:55
下一篇 2023年5月6日 19:55

相关推荐

发表评论

登录后才能评论

联系我们

在线咨询:1643011589-QQbutton

手机:13798586780

QQ/微信:1074760229

QQ群:551893940

工作时间:工作日9:00-18:00,节假日休息

关注微信