为什么iterator.remove不会抛出ConcurrentModificationException

iterator.remove与list.remove的不同之处在于,当list.remove抛出时,迭代器不会抛出exception吗? 最后两者都在修改集合大小。

请忽略此处的线程。 我只是谈论for-each循环和迭代器循环。 据我所知 – 每个循环仅在内部创建迭代器。

我很迷惑。

Iterator.remove()不会抛出ConcurrentModificationException ,因为这是在迭代时修改集合的允许方式。

如果您以任何其他方式更改正在迭代的集合,那么您可能会遇到exception。

如果在同一个集合中有两个迭代器,并且通过其中一个迭代器删除,则您也有可能获得exception。


iterator.remove与list.remove的不同之处在于,当list.remove抛出时,迭代器不会抛出exception吗?

这个交易是当你通过迭代器删除时,迭代器的实现能够更新其数据结构以考虑删除。 相反,如果通过集合对象删除(或插入或替换),则无法更新迭代器数据结构以使其与集合保持同步。

(还有一个问题是非并发集合类型未实现为线程安全的,因此如果集合和迭代器由不同的线程使用/更新,您也可能会出现exception。)

我想你的意思是,如果你在迭代一个列表,为什么list.remove()会引发一个ConcurrentModificationException ,而iterator.remove()却没有?

考虑这个例子:

  List list = new ArrayList<>(Arrays.asList("a", "b", "c", "d")); for (Iterator iter = list.iterator(); iter.hasNext(); ) { if (iter.next().equals("b")) { // iter.remove(); // #1 // list.remove("b"); // #2 } } 

如果取消注释第1行,它将正常工作。 如果取消注释第2行(但留下#1注释),那么它将导致对iter.next()的后续调用抛出ConcurrentModificationException

原因是迭代器是一个单独的对象,它具有对底层列表的内部状态的一些引用。 如果在迭代器运行时修改列表,则可能导致迭代器行为exception,例如跳过元素,重复元素,索引数组末尾等等。它会尝试检测此类修改,因此抛出ConcurrentModificationException如果是的话。

通过迭代器删除元素有效并且不会导致exception,因为这会更新基础列表迭代器引用列表内部的状态,因此一切都可以保持一致。

但是, iterator.remove()没有什么特别之处,它可以在所有情况下工作。 如果有多个迭代器在同一列表上进行迭代,则由一个迭代器进行的修改将导致其他迭代器出现问题。 考虑:

  Iterator i1 = list.iterator(); Iterator i2 = list.iterator(); i1.remove(); i2.remove(); 

我们现在有两个指向同一列表的迭代器。 如果我们使用其中一个修改列表,它会中断第二个的操作,因此对i2.remove()的调用将导致ConcurrentModificationException

因为它是抛出exception的迭代器。 如果你调用List.remove()它不知道删除,只知道它已经发生了变化。 如果你调用Iterator.remove()它知道当前元素已被删除以及如何处理它。

下面是一个示例,如果集合迭代器没有检查底层集合的修改,那么事情可能会出错。 这就是ArrayLists的迭代器的实现方式:

 private class Itr implements Iterator { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); // ... cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { // ... ArrayList.this.remove(lastRet); // ... cursor = lastRet; lastRet = -1; } 

我们来看一个例子:

 List list = new ArrayList(Arrays.asList(1, 2, 3, 4)); Iterator it = list.iterator(); Integer item = it.next(); 

我们删除了第一个元素

 list.remove(0); 

如果我们现在要调用it.remove() ,迭代器将删除数字2,因为这是lastRet现在指向的字段。

 if (item == 1) { it.remove(); // list contains 3, 4 } 

这是不正确的行为! 迭代器的契约声明remove()删除next()返回的最后一个元素,但是在并发修改的情况下它不能保持它的契约。 因此,它选择安全并抛出exception。

对于其他馆藏来说,情况可能更复杂。 如果修改HashMap ,它可能会根据需要增大或缩小。 那时,元素会落到不同的桶中,并且迭代器会在重新散列之前保持指向存储桶的指针。

请注意, iterator.remove()不会自行抛出exception,因为它能够更新自身的内部状态和集合。 但是,在同一个实例集合的两个迭代器上调用remove()会抛出,因为它会使其中一个迭代器处于不一致状态。

 public class ArrayListExceptionTest { public static void main(String[] args) { ArrayList list1 = new ArrayList<>(); list1.add("a"); list1.add("b"); list1.add("c"); Iterator it1 = list1.iterator(); ArrayList list2 = new ArrayList(); list2.add("a"); try { while (it1.hasNext()) { list1.add(it1.next()); } } catch (ConcurrentModificationException e) { e.printStackTrace(); } it1 = list1.iterator(); while (it1.hasNext()) { System.out.println(it1.next()); } it1 = list1.iterator(); try { while (it1.hasNext()) { if (it1.next().equals("a")) list1.retainAll(list2); } } catch (ConcurrentModificationException e) { e.printStackTrace(); } it1 = list1.iterator(); while (it1.hasNext()) { System.out.println(it1.next()); } it1 = list1.iterator(); Iterator it2 = list1.iterator(); it1.remove(); it2.remove(); } } 

你可以看到以上3个案例

情况1:通过添加元素进行修改,因此当使用next()函数时,它导致ConcurrentModificationException。

情况2:使用retain()进行修改,因此当使用next()函数时,会导致ConcurrentModificationException。

情况3:将抛出java.lang.IllegalStateException而不是ConcurrentModificationException。

输出:

 a b c a a a java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859) at com.rms.iteratortest.ArrayListExceptionTest.main(ArrayListExceptionTest.java:21) java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859) at com.rms.iteratortest.ArrayListExceptionTest.main(ArrayListExceptionTest.java:37) Exception in thread "main" java.lang.IllegalStateException at java.util.ArrayList$Itr.remove(ArrayList.java:872) at com.rms.iteratortest.ArrayListExceptionTest.main(ArrayListExceptionTest.java:55)