为什么会出现ConcurrentModificationException异常?
之前在“Java Collections Framework学习笔记之Collection接口”中在介绍Iterator时有提到过ConcurrentModificationException这个异常。我们被提醒:在直接使用Iterator(不是通过增强for循环间接使用)时,要记住一个基本法则:如果对正在被迭代的集合进行结构上的改变(如add、remove、clear),那么迭代器就不再合法(会抛出ConcurrentModificationException)。
那么,我们就具体来看看这个ConcurrentModificationException异常
环境为JDK1.8
看下面一段代码
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
if (integer == 2)
list.remove(integer);
}
}
运行报错:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
at java.util.AbstractList$Itr.next(Unknown Source)
我们根据上面程序的代码,一步步看ArrayList的源码:
由这一句
Iterator<Integer> iterator = list.iterator();
我们进入到ArrayList的iterator()方法的实现:
public Iterator<E> iterator() {
return new Itr();
}
从这段代码看到返回的是一个Itr类型对象的引用,接下来进入Itr()看其具体实现。在JDK1.8中,ArrayList中的Itr()是AbstractList.Itr的优化版。与在AbstractList中的Itr一样,在ArrayList中的Itr也是一个内部类
private class Itr implements Iterator<E> {
// 下一个要返回的元素的索引
int cursor;
// 返回的最后一个元素的索引,即上一个元素的索引,如果没有返回-1
int lastRet = -1;
// 对ArrayList修改次数的期望值,初始值为modCount
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: modCount = 0 modCount是AbstractList中的一个成员变量,表示对List的修改次数(每次add/remove都会使modCount+/-),其定义是protected transient int modCount = 0;
- expectedModCount = 0
- cursor = 0
- lastRet = -1
- size = 1
让我们的程序继续走
while (iterator.hasNext()) {
进入到iterator的hasNext()方法中
public boolean hasNext() {
return cursor != size; //
}
如果下一个元素的下标和ArrayList大小不相等,说明后面还有元素。这个很好理解,如果相等了,那就是访问到最后一个元素了。
当hasNext()返回true后,进入while内部,继续跟着程序走:
Integer integer = iterator.next();
进入到iterator的next()方法,我们的程序会通过next()获取到下标为0的元素
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor; // i = 0
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1; // cursor = 1
return (E) elementData[lastRet = i]; // lastRet = 0
}
此时变量的值为:
- modCount = 0
- expectedModCount = 0
- cursor = 1
- lastRet = 0
- size = 1
继续看我们的代码
if (integer == 2)
list.remove(integer);
如果当前元素值为2,则进入ArrayList的remove()方法
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
进入fastRemove()
private void fastRemove(int index) {
modCount++; // modCount = 1
//接下来删除元素
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // 置为null让GC工作
}
第一次while循环顺利完成,此时变量的值为:
- modCount = 1
- expectedModCount = 0
- cursor = 1
- lastRet = 0
- size = 0
接下来再次进入while,根据hasNext()判断cursor和size不等,继续进入循环内部,调用iterator的next()方法,在前面贴出来的next()方法中我们可以看到,会首先调用Itr类的checkForComodification()
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
因为此时的modCount = 1,而expectedModCount = 0,则抛出ConcurrentModificationException异常
关键点就在于:list.remove()使得modCount和expectedModCount值不相等
这也就解释了开头我们提到的,为什么“对正在被迭代的集合进行结构上的改变(如add、remove、clear),那么迭代器就不再合法(会抛出ConcurrentModificationException)”
在单线程环境下解决该异常
在Itr类中也有一个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();
}
}
这个remove方法也是调用了ArrayList的remove,但是多了一步expectedModCount = modCount; 所以删除时使用Itr的remove()就可以避免抛出ConcurrentModificationException了,不过要注意ArrayList迭代器的remove方法是不含参的,也就是说使用这个方法只能删除它刚看到的值(next()后)。
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
Integer integer = iterator.next();
if (integer == 2)
iterator.remove();
}
}