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

    科技2022-07-12  152

    一、前言

    最近项目中遇到一个异常问题,需求是转变数据库中的格式,需要循环每个list并对其进行add或remove长度增减操作,代码编译没问题,但运行后系统报错:

    java.util.ConcurrentModificationException: null at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429) ~[na:1.8.0_77] at java.util.HashMap$EntryIterator.next(HashMap.java:1463) ~[na:1.8.0_77] at java.util.HashMap$EntryIterator.next(HashMap.java:1461) ~[na:1.8.0_77]

    于是决定记录下问题,总结原因和解决方法。

    二、问题原因

    在ArrayList的父类AbstractList的源码中有iterator()迭代方法,其中实现为返回一个新建Itr()对象:

    public Iterator<E> iterator() { return new Itr(); }

    继续进入Itr()方法,AbstractList中一个内部成员类:

    protected transient int modCount = 0; private class Itr implements Iterator<E> { /** * Index of element to be returned by subsequent call to next. */ int cursor = 0; /** * Index of element returned by most recent call to next or * previous. Reset to -1 if this element is deleted by a call * to remove. */ int lastRet = -1; /** * The modCount value that the iterator believes that the backing * List should have. If this expectation is violated, the iterator * has detected concurrent modification. */ int expectedModCount = modCount; public boolean hasNext() { return cursor != size(); } public E next() { checkForComodification(); try { int i = cursor; E next = get(i); lastRet = i; cursor = i + 1; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.remove(lastRet); if (lastRet < cursor) cursor--; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException e) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }

    在这个具体实现类中,有以下四个成员变量:

    cursor:后续调用next返回元素的索引;lastRet:最近调用返回的元素的索引,如果调用remove删除该元素,则重置为-1;expectedModCount:对ArrayList修改次数的期望值,初始值为modCount;modCount:list的修改次数。调用add()和remove()方法就会对modCount进行加一操作

    当调用递归方法时,总是会先调用checkForComodification()方法,然后根据cursor的值获取到元素,之后将cursor的值付给lastRet,并对cursor的值进行加一操作。初始时,cursor=0,lastRet=-1,那么调用一次之后,cursor=1,lastRet=0。注意此时,modCount=0,expectedModCount=0。

    而在remove()方法中则是对modCount进行加一操作。然后接下来就是删除元素的操作,最后将size进行减一操作,并将引用置为null以方便垃圾收集器进行回收工作。那么注意此时各个变量的值:对于iterator,其expectedModCount=0,cursor=1,lastRet=0。对于list,其modCount=1,size=0。接着看程序代码,执行完删除操作后,继续while循环,调用hasNext()方法判断,由于此时cursor=1,而size=0,那么返回true,所以继续执行while循环,然后继续调用iterator的next()方法。

    如果modCount不等于expectedModCount,则抛出ConcurrentModificationException异常。很显然,此时modCount=1,而expectedModCount=0,因此程序就抛出了ConcurrentModificationException异常。因为在递归中调用list.remove()方法导致modCount和expectedModCount的值不一致。

     

    三、解决方法

    public ListIterator<E> listIterator(final int index) { rangeCheckForAdd(index); return new ListItr(index); } private class ListItr extends Itr implements ListIterator<E> { ListItr(int index) { cursor = index; } public boolean hasPrevious() { return cursor != 0; } public E previous() { checkForComodification(); try { int i = cursor - 1; E previous = get(i); lastRet = cursor = i; return previous; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } } public int nextIndex() { return cursor; } public int previousIndex() { return cursor-1; } public void set(E e) { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { AbstractList.this.set(lastRet, e); expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } public void add(E e) { checkForComodification(); try { int i = cursor; AbstractList.this.add(i, e); lastRet = -1; cursor = i + 1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } }

    通过查看源码可以发现在AbstractList类中调用listIterator()方法,其底层ListItr()的add()方法中expectedModCount = modCount;这时 checkForComodification()方法就不会再报错。所以如果业务场景中需要迭代对list进行增删元素时,使用ListIterator对象,调用他的remove()或add()方法即可。

     

    参考资料

    Java ConcurrentModificationException异常原因和解决方法

    Processed: 0.014, SQL: 8