ConcurrentModificationException异常产生原因分析

时间:2022-03-11 20:19:38

      在前段时间曾经写过一篇文章,关于如何在循环过程中删除集合中的元素,当使用foreach或者iterator进行循环迭代时,直接使用list.remove(index)或者list.remove(object),会抛出ConcurrentModificationException异常,今天就来分析一下异常产生的原因。
      首先看一下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;
}

      首先进行判断,当传入方法的object对象为空时,循环整个集合,根据得出索引删除第一个为空元素,当object不为空时,执行同样的步骤,如果以上两步任何一步有删除元素,则返回true。否则方法返回false。所以重点落在了fastRemove这个方法中。该方法的源码如下:

private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // 标记对象为空让GC去抓紧处理他
}

      在fastRemove代码中,先对modCount做自增操作,那么这个modCount这个变量是做什么的呢?找的该变量声明部分,即AbstractList,ArrayList的父类。通过源码中的介绍,可以发现每当List的结构发生变化时,都会都该变量进行操作,在源码注释中写道:

This field is used by the iterator and list iterator implementation returned by the {@code iterator} and {@code listIterator} methods. If the value of this field changes unexpectedly, the iterator (or list iterator) will throw a {@code ConcurrentModificationException} in response to the {@code next}, {@code remove}, {@code previous},{@code set} or {@code add} operations. This providesfail-fast behavior, rather than non-deterministic behavior in the face of concurrent modification during iteration.

      就是说这个变量被iterator或list iterator使用,被其中的方法修改,如果这个变量发生了不可预期的变化,就会在调用某些方法是抛出异常。这是提供一种快速失败的策略,而不是去面对并发修改时的不确定性。
      通过查阅,我知道了foreach方法最终执行时也是在调用iterator,这样我们就需要去看一下iterator在迭代过程中到底做了什么才会导致这个异常的发生。在使用iterator时我们会调用next()方法来取下一个元素,其代码如下:

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];
}

      可以看到,当调用next方法时,首先会执行checkForComodification()方法,该方法代码如下

final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

      由此可知,每次我们循环到下一个元素时,都会对modCount的值和expectedModCount的值进行比较,如果两个值不相等,则抛出ConcurrentModificationException异常。那么为什么使用iterator.remove(object)不会发生这种情况呢?看一下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();
}
}

      原因一看便知,iterator中的remove方法也是在调用list中remove(index)方法,所以也对modCount进行了修改,但是之后进行了expectedModCount = modCount操作,所以在此调用checkForComodification()方法时不会抛出异常。
      以上就是我对这个异常产生原因的理解,错误之处欢迎指正,谢谢。