Fail-fast
单线程操作iterator时(以ArrayList
为例)
List<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(2);
integers.add(3);
/** fail-fast in Iterator
* Throw ConcurrentModificationException
*/
Iterator<Integer> it = integers.iterator();
while(it.hasNext()) {
Integer integer = it.next();
integers.remove(integer);
}
// 程序运行结果如下
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
让我们通过观察JDK中ArrayList
类的源码来找到异常抛出的原因。
ArrayList
对象首先创建iterator
对象,这里返回的是ArrayList
的一个内部类
public Iterator<E> iterator() {
return new Itr();
}
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; // size会随list的增删而改变,但cursor只会在next()中自增
}
public E next() {
checkForComodification();
...
cursor = i + 1;
...
}
...
}
可以看出在创建iterator
对象时,会将list的modCount
赋值给expectedModCount
。
modCount
是list对象进行的add()
, remove()
等影响集合结构的方法的次数。在此例中,创建迭代器时modCount
值为3:add(1), add(2), add(3)。
而在遍历过程中,使用ArrayList
的remove()
方法会再次改变modCount
的值。
public boolean remove(Object o) {
...
fastRemove(index);
...
}
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; // clear to let GC do its work
}
这一操作却没有修改expectedModCount
的值。
当迭代器下一次执行next()
方法时,check发现modCount
(4)和expectedModCount
(3)不相等,故抛出异常。
public E next() {
checkForComodification();
...
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
单线程操作foreach
List<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(2);
integers.add(3);
for (Integer i : integers) {
integers.remove(i);
}
// 程序运行结果如下
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
由错误日志可以看出,foreach代码段的本质与iterator相同。
结论一
在fail-fast的集合对象中,不要在iterator或者foreach循环中进行元素的remove()
/add()
操作。这样的操作既会导致hasNext()
失真,也会在后面的next()
方法执行时抛出ConcurrentModificationException
异常。
结论二
上述情况下,想要删除元素可以用iterator
的remove()
方法,而不是ArrayList
的remove()
。
List<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(2);
integers.add(3);
Iterator<Integer> it = integers.iterator();
while (it.hasNext()) {
Integer i = it.next();
if (Objects.equals(i, new Integer(1)))
it.remove();
}
// 上述代码可以正确运行并删除值为1的元素
iterator
的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();
}
}
迭代器删除元素的同时会修改其cursor
和expectedModCount
。
Fail-safe
单线程操作list对象本身(以CopyOnWriteArrayList
为例)
List<Integer> integers = new CopyOnWriteArrayList<>();
integers.add(1);
integers.add(2);
integers.add(3);
Iterator<Integer> itr = integers.iterator();
while (itr.hasNext()) {
Integer a = itr.next();
integers.remove(a);
System.out.println(a);
}
// 将会成功将集合清空
CopyOnWriteArrayList
的迭代器
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
...
static final class COWIterator<E> implements ListIterator<E> {
/** Snapshot of the array */
private final Object[] snapshot;
/** Index of element to be returned by subsequent call to next. */
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements; // 这里不是指针的引用 是复制一个数组
}
...
}
这种迭代器将会复制一份snapshot
数组,游标也将在snapshot
数组上操作,原集合的操作不会影响迭代器的操作。
因此,在上例中,intergers
集合将变为空集合,而snapshot
在作用域内依然为[1, 2, 3]。
CopyOnWriteArrayList不再拥有remove()方法的正确实现
观察COWIterator
内部类的remove()
源码,将会发现,
public void remove() {
throw new UnsupportedOperationException();
}
此时若再使用迭代器的remove()
方法,程序将会出错。
结论
在fail-safe的集合对象里,我们可以直接对原集合进行增删而不会出现异常(单线程)。迭代器不再具有remove功能。