为什么在这种情况下没有使用LinkedList迭代器的ConcurrentModificationException?

时间:2014-11-28 20:59:46

标签: java linked-list

请考虑以下代码段:

List<String> list = new LinkedList<>();
list.add("Hello");
list.add("My");
list.add("Son");

for (String s: list){
    if (s.equals("My")) list.remove(s);
    System.out.printf("s=%s, list=%s\n",s,list.toString());
}

这导致输出:

s = Hello,list = [Hello,My,Son]
s = My,list = [Hello,Son]

很明显,循环只输入两次,第三个元素“Son”永远不会被访问。从底层库代码看,发生的情况是迭代器中的hasNext()方法不检查并发修改,只检查下一个索引的大小。由于remove()调用后大小减少了1,因此循环不会再次输入,但不会抛出ConcurrentModificationException。

这似乎与迭代器的契约相矛盾:

  

list-iterator是 fail-fast :如果在创建Iterator之后的任何时候对列表进行结构修改,除了通过list-iterator自己的remove或者add方法,list-iterator将抛出ConcurrentModificationException。因此,面对并发修改,迭代器会快速而干净地失败,而不是在未来不确定的时间冒着任意的,非确定性行为的风险。

这是一个错误吗?同样,迭代器的契约在这里看起来肯定是不服从的 - 列表的结构在迭代过程中由迭代器之外的其他东西进行结构修改。

2 个答案:

答案 0 :(得分:3)

阅读班级Javadoc:

  

请注意,迭代器的快速失败行为无法得到保证,因为一般来说,在存在非同步并发修改的情况下,不可能做出任何硬性保证。失败快速迭代器会尽最大努力抛出ConcurrentModificationException。因此,编写依赖于此异常的程序以确保其正确性是错误的:迭代器的快速失败行为应仅用于检测错误。

答案 1 :(得分:3)

来自https://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html

  

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

也就是说,迭代器会尽力抛出异常,但并不保证在所有情况下都这样做。

以下是一些关于快速迭代器失败如何工作以及如何实现的更多链接 - 以防有人感兴趣:

http://www.certpal.com/blogs/2009/09/iterators-fail-fast-vs-fail-safe/

http://javahungry.blogspot.com/2014/04/fail-fast-iterator-vs-fail-safe-iterator-difference-with-example-in-java.html

http://www.javaperformancetuning.com/articles/fastfail2.shtml

这是另一个SO问题,人们试图找出同样的事情:

Why isn't this code causing a ConcurrentModificationException?