一、前言
最近项目中遇到一个异常问题,需求是转变数据库中的格式,需要循环每个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()方法即可。