为什么iterator.remove不会抛出ConcurrentModificationException

jav*_*fan 15 java collections foreach iterator

iterator.remove()list.remove()迭代器不同的是什么不会抛出异常而list.remove()抛出异常?最后两者都在修改集合大小.

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

我很迷惑.

Stu*_*rks 27

我想你的意思是,如果你正在迭代一个列表,为什么会list.remove()导致ConcurrentModificationException抛出而iterator.remove()不是?

考虑这个例子:

    List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d"));

    for (Iterator<String> iter = list.iterator(); iter.hasNext(); ) {
        if (iter.next().equals("b")) {
            // iter.remove();    // #1
            // list.remove("b"); // #2
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

原因是迭代器是一个单独的对象,它具有对底层列表的内部状态的一些引用.如果修改该列表而迭代器是在运行中,它可能会导致迭代表现不好,例如通过跳过元件,重复的元素,索引关阵列等的端部,它尝试检测这样的修改和因此它抛出ConcurrentModificationException如果它确实.

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

然而,没有什么特别的,iterator.remove()这使它适用于所有情况.如果有多个迭代器遍历同一个列表,由一个所作的修改将导致其他问题.考虑:

    Iterator<String> i1 = list.iterator();
    Iterator<String> i2 = list.iterator();
    i1.remove();
    i2.remove();
Run Code Online (Sandbox Code Playgroud)

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

  • 这实际上是更正确的答案 (4认同)

Ste*_*n C 12

ConcurrentModificationException不是Iterator.remove()因为这是在迭代时修改集合的允许方式.这是什么的JavadocIterator说:

从底层集合中移除此迭代器返回的最后一个元素(可选操作).每次调用next()时,只能调用一次此方法.如果在迭代正在进行中以除调用此方法之外的任何方式修改基础集合,则未指定迭代器的行为.

如果您以任何其他方式更改正在迭代的集合,那么您可能会遇到异常,具体取决于迭代器的实现以及您正在迭代的集合(或其他).(一些集合类会不会给你ConcurrentModificationException:检查各自的javadoc,看看他们是如何规定的行为,他们的迭代器)

如果在同一个集合中有两个迭代器,并且通过其中一个迭代器删除,则您也可能会遇到异常.


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

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

为什么?因为它需要以下内容:

  • 需要从集合数据结构到所有现存迭代器对象的链接.此类链接存在内存泄漏风险或需要使用while对象以避免泄漏.

  • 在对集合的每次更新时,每个迭代器都需要"保持步调"更新.这会使收藏更新变得更加昂贵.

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

这些问题激发了集合API的设计选择.


我只是谈论for-each循环和迭代器循环.据我所知 - 每个循环仅在内部创建迭代器.

那是正确的.for-each循环实际上只是ConcurrentModificationException使用迭代器的循环的语法糖.

另一方面,如果你使用这样的循环:

    for (int i = 0; i < list.size(); i++) {
        if (...) {
            list.remove(i);
        }
    }
Run Code Online (Sandbox Code Playgroud)

你不会得到Reference,但是你需要为你删除的元素调整索引变量,而另一个线程的更新可能会导致你跳过元素或多次访问它们2.


2 - 甚至得到一个IndexOutOfBoundsException.如果集合不是并发/正确同步的,那么你可能会遇到更严重的问题.