为什么 iterator.remove 不抛出 ConcurrentModificationException

2022-09-01 21:39:14

有什么不同于 ,以便迭代器不会引发异常并引发异常?最后,两者都在修改集合大小。iterator.remove()list.remove()list.remove()

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

我很困惑。


答案 1

我想你的意思是,如果你正在迭代一个列表,为什么会导致一个被抛出而没有被抛出?list.remove()ConcurrentModificationExceptioniterator.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
        }
    }

如果您取消注释行#1,它将正常工作。如果取消注释行 #2(但保留 #1 注释),则会导致后续调用抛出 。iter.next()ConcurrentModificationException

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

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

但是,没有什么特别之处可以使其在所有情况下都有效。如果有多个迭代器迭代同一列表,则其中一个迭代器所做的修改会给其他迭代器带来问题。考虑:iterator.remove()

    Iterator<String> i1 = list.iterator();
    Iterator<String> i2 = list.iterator();
    i1.remove();
    i2.remove();

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


答案 2

ConcurrentModificationException不会被抛出,因为这是在迭代时修改集合的允许方法。这就是javadoc所说的:Iterator.remove()Iterator

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

如果以任何其他方式更改要迭代的集合,则可能会遇到异常,具体取决于迭代器的实现以及要迭代的集合(或其他任何内容)。(有些集合类不会给你一个:检查相应的javadocs,看看它们如何指定迭代器的行为)ConcurrentModificationException

如果在同一集合上有两个迭代器,并且通过其中一个迭代器进行删除,则也容易遇到异常。


什么 iterator.remove 与 list.remove 不同,即 iterator 不引发异常,而 list.remove 会抛出异常?

原因#1。如果从同一调用堆栈上的两个位置同时更新非并发集合,则该行为将破坏迭代1 的设计不变性。非并发集合的迭代保证只看到集合中的所有元素一次。(相比之下,对于并发集合,这些保证是宽松的。

原因#2。非并发收集类型未实现为线程安全。因此,如果使用集合和迭代器通过不同的线程更新集合,则可能存在争用条件和内存异常。这不是强有力的理由,因为无论如何你都会遇到这些问题。但是,以两种不同的方式进行更新会使问题变得更糟。


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

这是正确的。for-each 循环实际上只是使用迭代器的循环的语法糖。while

另一方面,如果您使用如下循环:

    for (int i = 0; i < list.size(); i++) {
        if (...) {
            list.remove(i);
        }
    }

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


1 - 为了实现“恰好一次”迭代行为,当您通过集合对象删除元素时,需要更新迭代器数据结构,以使其与集合发生的情况保持一致。这在当前的实现中是不可能的,因为它们不保留指向未完成迭代器的链接。如果他们这样做,他们将需要使用引用对象或面临内存泄漏的风险。

2 - 甚至得到一个IndexOutOfBoundsException。如果集合未并发/正确同步,则可能会遇到更严重的问题。