为什么在不安全的操作之后而不是在整个forEach循环之后抛出ConcurrentModificationException?

问题描述 投票:2回答:1

通常,在进行迭代的同时进行修改时,会立即引发ConcurrentModificationException。

但是,使用流API的forEachOrdered方法,每次修改后都不会引发异常。取而代之的是,修改可以在迭代过程中成功进行,并且最终在整个操作完成后会引发异常。有人可以告诉我这是怎么发生的吗?

下面的代码生成3个随机数,范围从-0.5到0.5。然后过滤掉负值。

        HashMap<Integer, Double> positiveNegative = new HashMap<>();
        for (int i = 0; i < 3; i++) {
            double value =Math.random() - 0.5;
            positiveNegative.put(i, value);
        }

        System.out.println(positiveNegative);

        //filter out the negative value
        positiveNegative.entrySet().stream()
                .filter((entry) -> (entry.getValue() < 0))
                .forEachOrdered(entry -> {
                    System.out.println(entry);
                    positiveNegative.remove(entry.getKey());
                    System.out.println(positiveNegative);
                });

        System.out.println(positiveNegative);
{0=-0.38785299800912576, 1=-0.11085161629013052, 2=-0.3516129030809366}
0=-0.38785299800912576
{1=-0.11085161629013052, 2=-0.3516129030809366}
1=-0.11085161629013052
{2=-0.3516129030809366}
2=-0.3516129030809366
{}

Exception in thread "main" java.util.ConcurrentModificationException
    at java.base/java.util.HashMap$EntrySpliterator.forEachRemaining(HashMap.java:1751)
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.base/java.util.stream.ReferencePipeline.forEachOrdered(ReferencePipeline.java:502)
    at Solution.main(Solution.java:51)
lambda java-8 java-stream
1个回答
5
投票

[the documentation说:

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

您的示例中不涉及并发,但重点仍然是;此功能仅用于检测错误,不能保证是否或何时引发异常。要检测该错误,即使仅在迭代结束时,抛出异常[[if也已足够。

hasNext()next()Iterator方法的情况下,无法控制调用者。不需要调用者迭代到集合的末尾,换句话说,在放弃Iterator之前,每个方法调用都可以是最后一个。因此,这些方法无法推迟检查。

[相反,forEachRemainingIteratorSpliterator方法在迭代循环中,并且知道它们何时退出,因此可以忽略每次迭代中的检查并在最后执行一次检查,假设我们有一个专门的实现,而不是default方法,该方法别无选择,只能反复调用单元素迭代方法。

如您在堆栈跟踪中所看到的,forEachOrdered流操作最终以HashMap$EntrySpliterator.forEachRemaining结尾,这是利用这一机会的专门实现。显而易见的原因是性能,因为当合同允许最后一次执行此操作时,无论如何您都不想浪费CPU周期来测试应该为false的每次迭代中的条件(如果我们从一份

尽力而为]基础合同)。

© www.soinside.com 2019 - 2024. All rights reserved.