冲天香阵透长安,满城尽带黄金甲
序
上次在写代码时,使用迭代器遍历一个集合,并在迭代中移除(或下新增)集合中的某项,就会出现ConcurrentModificationException异常。
问题复现
问题代码
public static void main(String[] args) {
//定义一个集合
List<String> list = new ArrayList<>();
//给集合添加数据
list.add("a");
list.add("b");
list.add("c");
//迭代集合
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
//拿到集合中的下一个值
String next = iterator.next();
System.out.println(next);
//移除集合中索引为1的数据
list.remove(1);
}
}
异常信息
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
原因分析
从异常信息中可以看出,是由next方法调用checkForComodification方法抛出的异常,接着我们找到ArrayList子类Itr下的next和checkForComodification方法,如下:
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();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
可以看到,在调用next方式,会先调用checkForComodification方法判断集合修改的次数和预期的值是否一致,不一致则会抛出ConcurrentModificationException异常。由控制台打印的信息可以看出,modCount != expectedModCount
,即抛出了该异常。
那么,为什么modCount != expectedModCount
,说明这个问题,我们接着看remove方法:
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
可以看到,在remove方法中,执行了modCount++;
,使得modCount的值加了1,而expectedModCount并没有变化,所以在remove之后,调用next时,next方法又调用checkForComodification判断方法,此时,modCount != expectedModCount
,就抛出了异常。
问题解决
解决这个问题也很简单,将代码中的//移除集合中索引为1的数据 list.remove(1);
改为迭代器的remove方法即可,即:iterator.remove();
。我们来看看迭代器的remove方法实现:
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();
}
}
方法里面有expectedModCount = modCount;
赋值操作,所以,在checkForComodification判断时就不会出异常了。