jdk文档解释:Iterator和ListIterator迭代器是快速失败的,在迭代器创建之后,如果从结构上对列表进行修改,除非通过迭代器自身的 remove 或 add 方法,其他任何时间任何方式的修改,迭代器都将抛出
ConcurrentModificationException
。因此,面对并发的修改,迭代器很快就会完全失败,而不冒将来不确定的时间任意发生不确定行为的风险。
下面通过分析LinkedList的源码来分析这种现象出现的原因,其他容易的类似:
首先给出AbstractList的定义:
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {}
在AbstractList类中定义了一个属性:protected transient int modCount = 0,该属性定义了列表结构上(增删)被修改的次数。
同时该类中定义了如下方法:
public Iterator<E> iterator() {
return new Itr();
}
其中Itr类的定义如下:
private class Itr implements Iterator<E> {
/**
* 此处只列出相关的属性和方法
*/
int expectedModCount = modCount;
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();
}
}
可以看出当获取当前对象的迭代器时,迭代器会将此时的modCount保存到expectedModCount,然后每次进行结构上的修改之前都会调用checkForComodification ()来检查expectedModCount是否等于modCount,若相等则说明迭代器生成之后没有通过调用原始链表的remove()等方法修改链表结构(若链表自己修改了结构如删除掉一个节点,会导致迭代器遍历过程中访问该节点时发现节点为空导致异常,所以为防止该异常,会尽早去发现是否可能出现该问题,从而进行“快速失败”抛出ConcurrentModificationException),然后调用LinkedList中的remove()方法来修改结构(下面会给出该方法,在该方法中会修改modCount++),结构修改之后迭代器需要修改expectedModCount以保证两者始终相等。
LinkedList的类定义:
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable{}
public abstract class AbstractSequentialList<E> extends AbstractList<E> {}
LinkedList中删除一个节点的代码如下:
/**
* Unlinks non -null node x.
*/
E unlink (Node<E> x) {
// assert x != null;
final E element = x. item;
final Node<E> next = x. next;
final Node<E> prev = x. prev;
if (prev == null) {
first = next;
} else {
prev. next = next;
x. prev = null;
}
if (next == null) {
last = prev;
} else {
next. prev = prev;
x. next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
正如jdk文档所说,ConcurrentModificationException异常的“快速失败”机制是不能得到保证的,一般来说,存在不同步的并发修改时,不可能作出任何硬性保证。快速失败迭代器尽最大努力抛出ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。