java.util.ConcurrentModificationException异常原因和解决方法

文章讲述了开发者在使用Java并行流处理大量数据并发插入数据库时遇到ConcurrentModificationException异常,分析了原因在于集合的非线程安全以及MyBatis批处理模式下的潜在问题,提出了解决方案,包括避免多线程操作和使用非批处理插入方式。
摘要由CSDN通过智能技术生成
记录一个问题今天在开发中遇到了这样一个问题

由于数据量比较大,采用批量插入手动提交事务,在使用stream流进行处理时,想着并行流多线程进行数据处理耗时会少一点,通过parallel方法开启并行流,处理完成后在用打算进行forEach遍历入库,这是代码简单贴一下

List<Map<Integer, String>> dataList = new ArrayList<>();
//获取sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
        try {
            //获取mapper
            MapperRepository mapper = sqlSession.getMapper(MapperRepository.class);
            //开启并行流
            dataList.stream().parallel()
                    //构建对象对数据进行处理赋值
                    .map(map -> ComplaintAppeal.builder()
                            ........
                            .build())
                    //插入
                    .forEach(mapper::insert);
            sqlSession.commit();
            sqlSession.clearCache();
        } catch (Exception e) {
            log.info("批量插入失败:" + e);
            //未提交的数据可以回滚
            sqlSession.rollback();
        } finally {
            sqlSession.close();
        }

 想法很美好,现实很骨感啊,进行forEach进行数据库插入时抛出了异常,其实只需要关注最后一句java.util.ConcurrentModificationException就好

异常信息
org.apache.ibatis.exceptions.PersistenceException: org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: java.util.ConcurrentModificationException
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### Cause: java.util.ConcurrentModificationException
分析下原因

在网上查询了一下出现java.util.ConcurrentModificationException异常的原因大概就是说集合并不是线程安全的所以才会出现这个异常

ConcurrentModificationException是基于java集合中的 快速失败(fail-fast) 机制产生的,在使用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了增删改,就会抛出该异常。快速失败机制使得java的集合类不能在多线程下并发修改,也不能在迭代过程中被修改。

在很多容器中,都有一个变量记录你从结构上修改此容器的次数,叫做modCount,查看ArrayList的add()和remove()方法就可以发现,每次你调用add方法()向容器里面增加了一个元素,或者你调用Remove()方法删除了其中的某个元素,这个值都会增加1。在对集合进行迭代的时候,这个值不能被改变,否则抛出异常ConcurrentModificationException。简单地说就是你在遍历的时候,你自己不会去改变这个值,但是你在遍历的过程中发现这个值被改变了,那只有一种解释,其他人修改了集合导致这个值改变了。

上面说集合类线程不安全,尝试调用工具类Collections.synchronizedList(dataList)将集合转换为线程安全的集合类,或是转换成CopyOnWriteArrayList,以为就可以解决问题了,很遗憾进行forEach插入的时候还是抛出了异常,想来这种安全的集合类也只能解决数组越界,数据丢失的问题

也有可能是mybatis的原因mybatis内部使用标签迭代HashMap的时候报ConcurrentModificationException异常。 通过源码分析,mybatis内部并没有迭代和删除并行,那就只有一种情况,传入mybatis的HashMap,是个全局变量,或者局部变量被其他线程所修改(添加元素或者删除元素),导致mybatis内部迭代的时候出现modCount!=expectedModCount导致异常。 解决方案:传入mybatis的HashMap不要存在异步操作或者全局变量被引入

解决方式

我的代码是因为批处理模式下多线程执行插入才报的错(在mybatis进行批量插入时,底层在记录日志输出的时候,获取参数调用了了list集合的next方法,内部modCount变量被修改了才抛出的异常,因为批处理本身就是插入大额数据集合,也不应该被我这样错误使用,记录一下引以为戒)去掉多线程操作,或者关闭批处理就可以解决报错问题

通过执行forEachOrdered方法这种方式可以保证数据按照顺序执行遗憾的是这个方法是单线程的跟我最初的想法相违背,毫无任何意义

也可以通过链式编程获取iterator迭代器对象执行forEachRemaining方法源码中其实就是一个while死循环遍历,这种方式也是单线程的所以没有任何意义

第三种方式就是放弃mybatis的BATCH批处理的方式

  • 13
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值