java.util.List的异常行为基于其中的元素数量

时间:2013-07-21 12:17:21

标签: java list collections iterator concurrentmodification

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

但它显示了不同的行为,具体取决于列表中的元素数量。

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

理想情况下,它应该在这种情况下抛出ConcurrentModificationException而不依赖于列表中的元素数量,但是当列表中的元素数量为2时,它不是真的。

案例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);
        }
    }
  

输出:一个

     

线程中的异常&#34; main&#34; 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);
        }
    }
  

输出:一个

     

线程中的异常&#34; main&#34; java.util.ConcurrentModificationException

再次引发异常,这是理想的行为。

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

3 个答案:

答案 0 :(得分:7)

来自documentation(强调我的):

  

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

     

请注意,无法保证迭代器的快速失败行为   一般来说,不可能做出任何硬性保证   存在不同步的并发修改。的快速失败   迭代器会尽最大努力抛出ConcurrentModificationException   基础。因此,编写一个依赖的程序是错误的   关于它的正确性的这个例外:的失败快速行为   迭代器只应用于检测错误

答案 1 :(得分:7)

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

如果你真的有兴趣看到为什么你只用两个元素就不会收到它(或者真的,如果你删除最后一个元素,无论大小如何),你可以看一下在source code for 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会根据大小检查当前光标位置。

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

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

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

请记住 - 这是实施细节,并不能保证。文档告诉您,您不应该依赖抛出(或不抛出)异常。可以用上面不再适用的异常方式重写ArrayList并抛出 异常(实际上,在另一个可能已经存在的JVM中)。

答案 2 :(得分:0)

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

试试这个:

Iterator.remove()

  

从底层集合中删除返回的最后一个元素   迭代器(可选操作)。此方法只能调用一次   每次打电话给下一个。如果是,则未指定迭代器的行为   迭代正在进行时修改底层集合   除了通过调用此方法之外的任何其他方式。