请考虑以下代码段:
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
。因此,面对并发修改,迭代器会快速而干净地失败,而不是在未来不确定的时间冒着任意的,非确定性行为的风险。
这是一个错误吗?同样,迭代器的契约在这里看起来肯定是不服从的 - 列表的结构在迭代过程中由迭代器之外的其他东西进行结构修改。
答案 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://www.javaperformancetuning.com/articles/fastfail2.shtml
这是另一个SO问题,人们试图找出同样的事情:
Why isn't this code causing a ConcurrentModificationException?