8

我知道如果在某个线程使用迭代器遍历它时更改集合,则 iterator.next() 将抛出ConcurrentModificationException

但它会根据列表中元素的数量显示不同的行为。

我尝试了一个代码片段,其中我在 for-each 循环中遍历了一个列表,并在遍历之间使用列表的 remove() 方法从列表中删除了一个元素。

理想情况下,它应该在这种情况下抛出 ConcurrentModificationException,而不取决于列表中的元素数量,但是当列表中的元素数量为两个时,情况并非如此。

案例 1: 列表中的元素数量 - 1

 public static void main(String[] args) 
    {
        List<String> list=new ArrayList<String>();
        list.add("One");

        for (String string : list) 
        {
            System.out.println(string);
            list.remove(string);
        }
    }

输出:一个

线程“主”java.util.ConcurrentModificationException 中的异常

果然不出所料。

案例 2:列表中的元素数量 - 2

 public static void main(String[] args) 
    {
        List<String> list=new ArrayList<String>();
        list.add("One");
        list.add("two");

        for (String string : list) 
        {
            System.out.println(string);
            list.remove(string);
        }
    }

输出:一个

没有抛出异常??????

案例 3:列表中的元素数量 - 3

 public static void main(String[] args) 
    {
        List<String> list=new ArrayList<String>();
        list.add("One");
        list.add("Two");
        list.add("Three");

        for (String string : list) 
        {
            System.out.println(string);
            list.remove(string);
        }
    }

输出:一个

线程“主”java.util.ConcurrentModificationException 中的异常

再次抛出异常,这是理想的行为。

但是为什么它在case-2中正常运行而没有抛出任何 ConcurrentModificationException。

4

3 回答 3

7

文档(强调我的):

此类的iteratorlistIterator 方法返回的迭代器是快速失败的:如果在创建迭代器后的任何时候列表在结构上被修改,除了通过迭代器自己的removeadd方法之外的任何方式,迭代器将抛出一个 ConcurrentModificationException. 因此,面对并发修改,迭代器快速而干净地失败,而不是在未来不确定的时间冒任意的、非确定性的行为。

请注意,不能保证迭代器的快速失败行为,因为一般来说,在存在不同步的并发修改的情况下,不可能做出任何硬保证。快速失败的迭代器ConcurrentModificationException在尽力而为的基础上抛出。因此,编写一个依赖于这个异常的正确性的程序是错误的:迭代器的快速失败行为应该只用于检测错误

于 2013-07-21T12:26:38.027 回答
7

之前发布的答案向您展示了解释为什么这是适当行为的相关文档;不保证您会收到该例外。

如果你真的想知道为什么你没有收到它只有两个元素(或者真的,如果你删除了倒数第二个元素而不管大小),你可以查看ArrayList.

你的循环:

for (String string : list)

实际上是:

for(Iterator<String> i = list.iterator(); i.hasNext(); ) {
    String string = i.next();
    ...
}

在 Arraylist 内部有一个int size表示ArrayList. 调用时会减少remove()

在里面Iterator有一个int cursor代表当前位置(索引)的Iterator。当你打电话时它会增加next()

hasNext()Iterator检查当前光标位置与大小。

在您的示例中,事件链如下:

  • cursor开始于0,size开始于2
  • next()被调用,cursor递增到1
  • remove()被调用,size递减为1
  • hasNext()比较cursorsize发现它们相同,返回false
  • 循环退出而不抛出异常

因此,如果您ArrayList在迭代时删除任何大小的倒数第二个元素,您将不会收到异常。(另外值得注意的是,您永远不会在循环中处理该列表中的最后一个元素;您的示例仅One出于该原因打印)。

但请记住 - 这是一个实现细节,不能保证。文档告诉您,您不应依赖被抛出(或未抛出)的异常。ArrayList可以以不同的方式重写,其中上述不再适用并引发异常(事实上,在另一个 JVM 中可能已经是这种情况)。

于 2013-07-21T13:28:50.057 回答
0

似乎当您从列表中删除指定元素时,列表不知道其大小已更改。

尝试这个:

迭代器.remove()

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

于 2013-07-21T12:24:17.307 回答