线上问题整理-ConcurrentModificationException异常

news/2025/2/14 2:16:17/

项目场景:

商品改价:商品改价中通过多线程批量处理经过 Lists.partition拆分的集合对象


问题描述

商品改价中通过多线程批量处理经过 Lists.partition拆分的集合对象,发现偶尔会报

java.util.ConcurrentModificationException: nullat java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1231)at java.util.ArrayList$SubList.spliterator(ArrayList.java:1235)at java.util.Collection.stream(Collection.java:581)

代码实现逻辑

 for (Map.Entry<Integer, List<PriceHandle>> partnerIdProductId : priceHandleGroup.entrySet()) {Integer partnerId = partnerIdProductId.getKey();List<PriceHandle> priceHandles = partnerIdProductId.getValue();log.info("当前分片={},开始同步partnerId={} size={}", shardingItem, partnerId, priceHandles.size());List<List<PriceHandle>> pidsGroup = Lists.partition(priceHandles, splitSize);log.info("partnerId={},分为{}组,每组{}条", partnerId, pidsGroup.size(), splitSize);CountDownLatch countDownLatch = new CountDownLatch(pidsGroup.size());for (List<PriceHandle> priceHandlesLittle : pidsGroup) {executorService.submit(() -> {try {List<Long> productIds = priceHandlesLittle.stream().map(PriceHandle::getProductId).collect(Collectors.toList());List<ProductMapEntity> productMapEntities = productMappingAllPartnerService.getProductMapListByPartnerIdAndProductIds(partnerId, productIds);//没有映射的失败不处理List<Long> mapProductIds = productMapEntities.stream().map(ProductMapEntity::getProductId).collect(Collectors.toList());List<PriceHandle> noMapPriceHandles = priceHandlesLittle.stream().filter(e -> !mapProductIds.contains(e.getProductId())).collect(Collectors.toList());if (CollectionUtils.isNotEmpty(noMapPriceHandles)) {jingdongPriceSyncService.updateBatch(noMapPriceHandles, EventStatus.HANDLED_NO_NEED.eventStatus, "商品无映射");}priceHandlesLittle.removeAll(noMapPriceHandles);// 过滤productPool中不存在的重复品filterRepeatProductId(productMapEntities, priceHandlesLittle);if (CollectionUtils.isEmpty(productMapEntities) || CollectionUtils.isEmpty(priceHandlesLittle)) {log.info("过滤productPool中不存在的重复品");return;}PriceSyncService.synPrice(partnerId, productMapEntities, false, priceHandlesLittle);} catch (Exception e) {log.error("同步价格异常,当前处理的店铺:{}", partnerId, e);} finally {countDownLatch.countDown();}});}try {countDownLatch.await();} catch (InterruptedException e) {log.error("countDownLatch.await error", e);Thread.currentThread().interrupt();}}

原因分析:

这里需要注意的是在使用Google Guava库中的Lists.partition方法时,如果对原始列表进行了修改,则可能会导致ConcurrentModificationException异常的抛出。这是因为Lists.partition方法返回的是原始列表的视图,而不是副本。因此,如果在对视图进行操作时修改了原始列表,则会导致ConcurrentModificationException异常的抛出。
因此在我们的这段代码中 问题在于又通过拆分后的subList做了removeAll的操作


具体复现
在这里插入图片描述

解决方案:

方法1不要再for循环中基于视图 修改修改原始列表
方法2 在for循环中新建一个list对象来包装原来的list
具体实现代码

for (Map.Entry<Integer, List<PriceHandle>> partnerIdProductId : priceHandleGroup.entrySet()) {Integer partnerId = partnerIdProductId.getKey();List<PriceHandle> priceHandles = partnerIdProductId.getValue();log.info("当前分片={},开始同步partnerId={} size={}", shardingItem, partnerId, priceHandles.size());List<List<PriceHandle>> pidsGroup = Lists.partition(priceHandles, splitSize);log.info("partnerId={},分为{}组,每组{}条", partnerId, pidsGroup.size(), splitSize);CountDownLatch countDownLatch = new CountDownLatch(pidsGroup.size());for (List<PriceHandle> priceHandlesLittle : pidsGroup) {// 由于存在并发remove,需要重新赋值新ArrayList,防止ArrayList$SubList引起的ConcurrentModificationExceptionList<PriceHandle> copyPriceHandlesLittle = new ArrayList<>(priceHandlesLittle);executorService.submit(() -> {try {List<Long> productIds = copyPriceHandlesLittle.stream().map(PriceHandle::getProductId).collect(Collectors.toList());List<ProductMapEntity> productMapEntities = productMappingAllPartnerService.getProductMapListByPartnerIdAndProductIds(partnerId, productIds);//没有映射的失败不处理List<Long> mapProductIds = productMapEntities.stream().map(ProductMapEntity::getProductId).collect(Collectors.toList());List<PriceHandle> noMapPriceHandles = copyPriceHandlesLittle.stream().filter(e -> !mapProductIds.contains(e.getProductId())).collect(Collectors.toList());if (CollectionUtils.isNotEmpty(noMapPriceHandles)) {PriceSyncService.updateBatch(noMapPriceHandles, EventStatus.HANDLED_NO_NEED.eventStatus, "商品无映射");}copyPriceHandlesLittle.removeAll(noMapPriceHandles);// 过滤productPool中不存在的重复品filterRepeatProductId(productMapEntities, copyPriceHandlesLittle);if (CollectionUtils.isEmpty(productMapEntities) || CollectionUtils.isEmpty(copyPriceHandlesLittle)) {log.info("过滤productPool中不存在的重复品");return;}PriceSyncService.synJPrice(partnerId, productMapEntities, false, copyPriceHandlesLittle);} catch (Exception e) {log.error("同步价格异常,当前处理的店铺:{}", partnerId, e);} finally {countDownLatch.countDown();}});}try {countDownLatch.await();} catch (InterruptedException e) {log.error("countDownLatch.await error", e);Thread.currentThread().interrupt();}}

参考
https://blog.csdn.net/weixin_48321825/article/details/121012733?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-121012733-blog-102456357.235v38pc_relevant_default_base3&spm=1001.2101.3001.4242.1&utm_relevant_index=3


http://www.ppmy.cn/news/1245436.html

相关文章

RabbitMQ之消费者可靠性

文章目录 前言一、消费者确认机制二、失败重试机制三、失败处理策略四、业务幂等性唯一消息ID业务判断 五、兜底方案总结 前言 当RabbitMQ向消费者投递消息以后&#xff0c;需要知道消费者的处理状态如何。因为消息投递给消费者并不代表就一定被正确消费了&#xff0c;可能出现…

docker (简介、dcoker详细安装步骤、常用命令)- day01

一、 为什么出现 Docker是基于Go语言实现的云开源项目。 Docker的主要目标是“Build&#xff0c;Ship and Run Any App,Anywhere”&#xff0c;也就是通过对应用组件的封装、分发、部署、运行等生命周期的管理&#xff0c;使用户的APP&#xff08;可以是一个WEB应用或数据库应…

回归预测 | MATLAB实现SCN随机配置网络多输入单输出回归预测

回归预测 | MATLAB实现SCN随机配置网络多输入单输出回归预测 目录 回归预测 | MATLAB实现SCN随机配置网络多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现SCN随机配置网络多变量回归预测 1.data为数据集&#xff0c;7个输入特征&#xff0…

【Android Gradle】之一小时 Gradle及 wrapper 入门

&#x1f604;作者简介&#xff1a; 小曾同学.com,一个致力于测试开发的博主⛽️&#xff0c;主要职责&#xff1a;测试开发、CI/CD 如果文章知识点有错误的地方&#xff0c;还请大家指正&#xff0c;让我们一起学习&#xff0c;一起进步。 &#x1f60a; 座右铭&#xff1a;不…

OpenCV快速入门:移动物体检测和目标跟踪

文章目录 前言一、移动物体检测和目标跟踪简介1.1 移动物体检测的基本概念1.2 移动物体检测算法的类型1.3 目标跟踪的基本概念1.4 目标跟踪算法的类型 二、差值法检测移动物体2.1 差值法原理2.2 差值法公式2.3 代码实现2.3.1 视频或摄像头检测移动物体2.3.2 随机动画生成的移动…

获取验证码在倒计时未完成前清除验证码

场景&#xff1a; 在点击获取验证码后&#xff0c;验证码开始倒计时&#xff0c;在点击登录后&#xff0c;出现弹窗不跳转页面。因此在出现弹窗后&#xff0c;即使倒计时没有结束&#xff0c;也要将倒计时的文字变为重新获取验证码。 template代码 <div class"form-b…

【斗破年番】萧炎斩杀蝎山,活捉魂殿铁护法,救小医仙身中魔斑毒

Hello,小伙伴们&#xff0c;我是拾荒君。 《斗破苍穹年番》第72集的国漫已经更新了。这一集中&#xff0c;蝎毕岩靠着秘术的加成暂时压制住了小医仙。在激烈的交战中&#xff0c;小医仙不得不解开自身的厄难毒体&#xff0c;而每解开一次&#xff0c;她就离死亡更近一步。 萧炎…

【SpringCloud】设计原则之分层架构与统一通信协议

一、设计原则之分层架构 应用分层看起来很简单&#xff0c;但每个程序员都有自己的一套方法&#xff0c;哪怕是初学者&#xff0c;所以实施起来并非易事 最早接触的分层架构应该是最熟悉的 MVC&#xff08;Model - View - Controller&#xff09;架构&#xff0c;其将应用分成…