【发布时间】:2015-01-27 12:52:00
【问题描述】:
考虑以下代码sn-p:
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=你好,列表=[你好,我的儿子]
s=My, list=[你好,儿子]
很明显,循环只输入了两次,第三个元素“Son”从未被访问过。从底层库代码看来,迭代器中的hasNext() 方法不检查并发修改,只检查下一个索引的大小。由于remove() 调用将大小减少了 1,因此不会再次进入循环,但不会引发 ConcurrentModificationException。
这似乎与迭代器的约定相矛盾:
list-iterator 是 fail-fast:如果列表在 Iterator 创建后的任何时间在结构上被修改,除了通过 list-iterator 自己的
remove或 @987654325 之外的任何方式@ 方法,列表迭代器将抛出ConcurrentModificationException。因此,面对并发修改,迭代器会快速而干净地失败,而不是在未来不确定的时间冒任意的、非确定性的行为。
这是一个错误吗?再一次,迭代器的约定在这里显然被违反了——列表的结构在迭代过程中被迭代器以外的东西在结构上修改了。
【问题讨论】:
-
有些人指向 LinkedList 类级别的文档,该文档给出了一些免责声明,即 ConcurrentModificationException 是基于“尽力而为”的。除非有人能证明为什么将其添加到
hasNext()会因某种原因出现问题,否则这似乎根本不是最好的努力。 -
我可能在这里说的很明显,但我能想到的不实施
hasNext()方法中的修改检查的唯一原因是它会保证在 所有种情况,而没有这种检查只会在一些种情况下导致不抛出异常,当执行修改导致立即退出循环时(例如,当所有元素从迭代器的当前位置到容器的末尾被删除),我猜这在实现者看来是相对罕见的。
标签: java linked-list