【问题标题】:Why no ConcurrentModificationException in this situation with LinkedList iterator? [duplicate]为什么在这种情况下使用 LinkedList 迭代器没有 ConcurrentModificationException? [复制]
【发布时间】: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


【解决方案1】:

阅读类级 Javadoc:

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

【讨论】:

  • 还是编译器的特性在下面使用了迭代器?
  • 它与编译器无关,与它完全不可能实现,或者至少极其不切实际有关。
  • 让我打开源代码看看有什么
  • 我明白了,谢谢。我认为应该更改迭代器文档的措辞。从我上面引用的部分直接来自迭代器文档,不清楚它不能保证,即它应该说“可能抛出”。
  • 投掷可能会更加激进,例如签入hasNext(),但即便如此,它也不能保证行为。 FWIW,我相信它的文档是继承的,这可能与它有关。
【解决方案2】:

来自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?

【讨论】:

  • 我只是要引用同样的话。提交错误报告可能值得,但您实际上不能依赖它来引发异常。
  • @markspace: 好吧,迭代器抛出异常的能力肯定是有用的,并且在很多情况下可能会为你省去一些麻烦,但这仍然不是保证,也不意味着你应该' t 尽量避免以不当方式修改 List。我想它更像是一条安全带:你最好系上它,但这并不意味着你可以安全地以 60 英里/小时的速度驶入灯柱。
  • @striving_coder 它根本没有尽力而为。虽然在涉及多个线程的复杂情况下可能无法保证这一点,但这几乎是一个基本的用例。
  • @PeteyPabPro:这里javahungry.blogspot.com/2014/04/… 说它实际上是在hasNext()next() 方法起作用时检查容器修改,因为你在这里争论是实现它的最佳方法.
  • @striving_coder 它只检查next(),而不是hasNext()
猜你喜欢
  • 2011-04-16
  • 1970-01-01
  • 2017-03-05
  • 2017-11-21
  • 2016-06-04
  • 2020-08-19
  • 1970-01-01
  • 1970-01-01
  • 2020-08-18
相关资源
最近更新 更多