记一次ConcurrentModificationException异常

非多线程,非高并发程序,出现ConcurrentModificationException异常

业务场景详情描述

  1. 业务需求:A模块有责任部门(单独字段)和入选部门(一个集合多条数据),我来做B模块:带出A模块的入选部门(一个集合多条数据),如果入选的部门集合中有责任部门,则把责任部门设置为Y,其他部门设置为N。如果入选的部门集合中没有责任部门,去查出A模块中责任部门后补充进入选部门集合中,同样把责任部门设置为Y,其他部门设置为N。
  2. 实现情况:主体为业务对象,并非部门,因此部门集合作为业务对象的一个属性成员变量展示(把集合操作完成后赋值给业务对象)。
  3. 我欠欠地使用了Lambda表达式去修改集合。。。

错误代码脱敏如下

//责任部门是否在入选部门集合中
if(null != deptList && deptList .size()>0) {//集合不为空,再遍历判断
 deptList.forEach(dept->{
	 if(!dutyDept.isEmpty()) {//责任部门不为空dutyDept为责任部门的ID
		 if(dutyDept.equals(dept.getId())) { //是:此集合包含了责任部门,原集合赋给主对象mainObject
			 mainObject.setDeptList(deptList);
		 }else {//不是:此集合 不 包含 责任部门=加入一个对象(责任部门)到集合
			 Dept d = new Dept();
			 d.set...(...);//设置元素其它属性...
			 .....
			 d.setIsOwner("Y");
			 //保存对象
			 dService.save(d);
			 //添加到集合中
			 deptList.add(0, d);
			 mainObject.setDeptList(deptList);
		 }
		 ......
	 }else {//责任部门为空
		...
	 }
 });
}else {//集合为空,直接添加
	......
}
  1. .forEach()
  2. .add(0, d)
    遍历时增加元素:造成ConcurrentModificationException异常

于是我学习了一下ConcurrentModificationException异常的前因后果

读的这篇文章

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Jiangshan11/article/details/83038857

总结如下

  1. ArrayList源码add如下
public void add(E e) {
	//抛出ConcurrentModificationException异常的地方
    checkForComodification();

    try {
        int i = cursor;
        ArrayList.this.add(i, e);
        cursor = i + 1;
        lastRet = -1;
        expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}
/////////checkForComodification();/////抛出异常的地方//////////////////
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

checkForComodification();所在方法

/**
     * An optimized version of AbstractList.Itr
     */
private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size) {
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        cursor = i;
        lastRet = i - 1;
        checkForComodification();
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

modCount就是修改次数,在具体的实现类中的Iterator中才会使用。在List集合中,ArrayList是List接口的实现类,modCount:表示list集合结构上被修改的次数。(在ArrayList所有涉及结构变化的方法中,都增加了modCount的值)list结构上别修改是指:改变了list的长度的大小或者是遍历结果中产生了不正确的结果的方式。add()和remove()方法会是modCount进行+1操作。modCount被修改后会产生ConcurrentModificationException异常, 这是jdk的快速失败原则。

问题解决方案

单线程情况:
(1)使用Iterator提供的remove方法,用于删除当前元素。
(2)建立一个集合,记录需要删除的元素,之后统一删除。
(3)不使用Iterator进行遍历,需要之一的是自己保证索引正常。
(4)使用并发集合类来避免ConcurrentModificationException,比如使用CopyOnArrayList,而不是ArrayList。
多线程情况:
(1)使用并发集合类,如使用ConcurrentHashMap或者CopyOnWriteArrayList。

此次错误解决方案

==用标记替代修改:遍历时做标记,处理标记修改集合,这样将两个动作分开做。==代码如下:

//查询出部门集合
List<Dept> deptList= xxxService.findDept(xxx);
//查询出此项目责任部门ID
String dutyDept = xxx.getId();
//责任部门是否在部门集合中:标记后统一处理
Boolean flagForEdpt = false;
if(null != deptList && deptList.size()>0) {//集合不为空,再遍历判断
	 for(int i=0;i<deptList.size();i++) {
		 if(dutyDept.equals(deptList.get(i).getId())) { //是:此集合包含了责任部门
			 flagForEdpt =true;
			 break; //直接跳出
		 }else {
			 flagForEdpt = false; //否:此集合不包含责任部门
		 }
 }
}else {//集合为空-直接标记为需要修改
 	flagForEdpt = false;
}
if(flagForEdpt == false) {//缺少责任部门
	 Dept d = new Dept();
	 d.set...(...);//设置元素其它属性...
	  //保存对象
	 dService.save(d);
	 //添加到集合中
	 deptList.add(0, d);
	}else if(flagForEdpt == true){//已包含责任部门
	 //处理一下......
}
mainObject.setDeptList(deptList);
发布了11 篇原创文章 · 获赞 0 · 访问量 220

猜你喜欢

转载自blog.csdn.net/weixin_43885975/article/details/105701347
今日推荐