每个stream的Java流StreamingModificationException异常行为

时间:2018-06-22 12:26:09

标签: java list lambda java-8 java-stream

当我运行以下代码时

List<Integer> list = IntStream.range(0,10).boxed().collect(Collectors.toList());
list.stream().forEach(i -> { 
    System.out.println("i:" +i);
    if (i==5) {
        System.out.println("..adding 22");
        list.add(22);
    }   
});

我得到以下输出:

i:0
i:1
i:2
i:3
i:4
i:5
..adding 22
i:6
i:7
i:8
i:9
Exception in thread "main" java.util.ConcurrentModificationException

为什么代码超出索引5?我不会在输出中看到以下几行:

i:6
i:7
i:8
i:9

我在这里遗漏了一些有关forEach行为的信息。 该文档确实声明“此操作的行为是明确不确定的。”然后继续讨论并行流。我希望并行流以任何顺序执行forEach,但是可以肯定串行流以串行方式执行提供给forEach的使用者吗?如果是这样,为什么Java允许代码扩展到索引5生成的异常之外呢?这里有一个线程,对吗?

先谢谢您。

编辑: 到目前为止,谢谢您的回答。绝对清楚,我的意思是,如果我这样做:

   for(int i: list){
        System.out.println("i:" +i);
        if(i==5) {
            System.out.println("..adding 22"); 
            list.add(22);
        }
    }

我明白了:

i:0
i:1
i:2
i:3
i:4
i:5
..adding 22
Exception in thread "main" java.util.ConcurrentModificationException

但是我不能在forEach中得到。 因此,似乎forEach的串行流与迭代器没有任何区别,无论是手动启动(Iterator iter = list.iterator ...)还是增强的for循环迭代器。这对我来说是意外的。但是从答案看来,这似乎是出于“性能原因”。但这仍然是...意想不到的。 只是为了踢球,我尝试了一个100万个元素的列表:

    List<Integer> list = IntStream.range(0,1000000).boxed().collect(Collectors.toList());
    list.stream().forEach(
            i -> { 
                if(i%250000==0)
                    System.out.println("i:" +i);
                if(i>999997)
                    System.out.println("i:" +i);

                if(i==5) {
                    System.out.println("..adding 22"); 
                    list.add(22);
            }}                        
            );

我得到了(现在预期的)以下输出:

i:0
..adding 22
i:250000
i:500000
i:750000
i:999998
i:999999
Exception in thread "main" java.util.ConcurrentModificationException

如上所述,检查似乎已经结束。

3 个答案:

答案 0 :(得分:8)

您看到的行为特定于ArrayListSpliteratorStream上使用的ArrayList

代码中的注释说明了实现选择:

  

ConcurrentModificationException(对性能最敏感的方法)[JDK 8 source code]

的末尾,我们仅执行一次forEach检查

这与并发修改检查的约定一致。在进行修改的情况下,不需要迭代器快速失败。如果他们确实选择快速失败,则实施者可以决定实施检查的严格程度。例如,如上所述,通常需要在正确性和性能之间进行权衡。

  

检测到对象并发修改的方法可能抛出此异常。 [...]快速运行操作会尽最大努力抛出ConcurrentModificationException。因此,编写依赖于此异常的程序的正确性是错误的:ConcurrentModificationException仅应用于检测错误。 [Java SE 8 API docs]

答案 1 :(得分:5)

好吧,在ArrayListSpliterator下有一条评论:

  

如果ArrayList是不可变的或结构上不可变的(没有添加,删除等),我们可以使用Arrays.spliterator实现它们的分隔符。取而代之的是,我们在遍历过程中发现了尽可能多的干扰,而又不牺牲太多性能

因此,没有按元素进行对何时在源List上发生干扰的检查(可能我对实现没有过多研究),但是在其他时候,主要目标将是不要牺牲性能,但是在“编辑”源代码时仍然会失败。

您仍然违反non-interference,并且在这种情况下,您的代码仍然会失败,而不是更快。

答案 2 :(得分:-1)

  

更新您的代码:使用CopyOnWriteArrayList

Error in charToDate(x) : 
  character string is not in a standard unambiguous format