Java程序在遍历一个非线程安全的List或Map时,因并发修改很容易出现ConcurrentModificationException,根本原因是遍历时使用的iterator是fail-fast的,也就是说,它认为并发修改是一种不被允许的异常情况,只要出现了就应该尽快失败。
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();
}
}
无论是显式地调用iterator()还是通过for-each遍历一个list,都是通过上面的Itr类,它会初始化一个expectedModCount变量,这个变量后面不会变化,除非显式地通过Itr类的remove()方法来移除元素;在next()和remove()方法开始的地方,都会调用checkForComodification()是否有并发修改,当modCount != expectedModCount时就会抛出异常。
modCount是在外围类AbstractList中定义的变量,它记录了这个list被结构性修改(指增加或者删除元素,文中所说的修改都是指结构性修改)的次数。当list增加或者删除元素的时候,都会有modCount++。例如下面的remove()方法(注意这里和上面remove()的区别是:不会改变iterator的expectedModCount):
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;
}
综上所述:
- 像
ArrayList,LinkedList,HashMap这些非线程安全的容器,在遍历时若有其他线程对容器做了结构性修改,都可能抛出ConcurrentModificationException。 - 即使在同一个线程中,在遍历的同时如果调用容器的
remove()方法来删除元素,也有可能抛出ConcurrentModificationException,正确的做法是用iterator的remove()来删除。 - 设计这个异常的原因是:设计者认为对非线程安全的容器的并发修改是一种非法情况,出现了就应该立即失败。对于需要并发修改的情况,应该使用线程安全的容器。
本文解析了Java中遍历非线程安全集合如ArrayList时,因并发修改导致ConcurrentModificationException的原因及解决方法。

756

被折叠的 条评论
为什么被折叠?



