java.util.ConcurrentModificationException Streams

时间:2018-12-25 15:35:26

标签: java java-8 java-stream concurrentmodification

我正在尝试以下代码 Java 8 SE 我直接从eclipse运行,它也有下面提到的异常 使用命令提示符运行它会产生相同的结果。

List<String> test = new ArrayList<>();
test.add("A");
test.add("B");
test.add("c");
test = test.subList(0, 2);
Stream<String> s = test.stream();
test.add("d");
s.forEach(System.out::println);

我不确定为什么它会给出以下异常

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1388)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)

我运行的Java版本

java version "1.8.0_171"
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)

4 个答案:

答案 0 :(得分:4)

这是因为subList,让我用不同的场景进行解释

来自Java文档 docs

  

对于行为良好的流源,可以在终端操作开始之前对其进行修改,并且这些修改将反映在所涵盖的元素中。

     

除了转义hatch操作iterator()和spliterator()之外,执行在调用终端操作时开始,并在终端操作完成时结束。

情况1:成功(因为可以在终端操作开始之前修改源)

List<String> test = new ArrayList<>();
    test.add("A");
    test.add("B");
    test.add("c");
    //test = test.subList(0, 2);
    Stream s = test.stream();
    test.add("d");
    s.forEach(System.out::println);

输出:

A
B
c
d

情况2:无法获得sublist个不同的参考

List<String> test = new ArrayList<>();
    test.add("A");
    test.add("B");
    test.add("c");
    List<String> test1 = test.subList(0, 2);
    Stream s = test1.stream();
    test1.add("d");
    s.forEach(System.out::println);

输出:

A
BException in thread "main" 
java.util.ConcurrentModificationException
at 
java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1388)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
at com.demo.Example.Main2.main(Main2.java:30)

情况3:对于sublist相同的引用失败,两个列表都不相同

List<String> test = new ArrayList<>();
    test.add("A");
    test.add("B");
    test.add("c");
    System.out.println(test.hashCode()); //94401
    test = test.subList(0, 2);
    System.out.println(test.hashCode()); //3042
    Stream s = test.stream();
    test.add("d");
    s.forEach(System.out::println);

输出:

94401
3042
A
B
Exception in thread "main" java.util.ConcurrentModificationException
at 
java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1388)
at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
at com.demo.Example.Main2.main(Main2.java:32)

最终结论

  
    

如果后备列表(即此列表)以结构方式(而不是通过返回的列表)进行了修改,则此方法返回的列表的语义将变得不确定。 (结构修改是指更改此列表大小或以其他方式干扰列表的方式,以使得正在进行的迭代可能会产生错误的结果。。)

  

来自文档子列表 docs

答案 1 :(得分:4)

最小代码

List<String> test = new ArrayList<>(Arrays.asList("java-8", "subList", "bug")).subList(0, 2);
Stream<String> stream = test.stream();
test.add("java-9");
stream.forEach(System.out::println); // any terminal operation

Java-8 [错误]

上面用Java-8执行的代码将引发CME。根据{{​​3}}

  

此类的迭代器和listIterator返回的迭代器   方法是 fail-fast :如果列表在任何情况下都经过结构修改   创建迭代器之后的时间,除了通过   迭代器自己的remove或add方法,迭代器会抛出一个   ConcurrentModificationException

     

因此,面对并发   修改后,迭代器会快速干净地失败,而不是   在不确定的时间冒任意,不确定的行为的风险   将来。

输出

java-8
subList
Exception in thread "main" java.util.ConcurrentModificationException

问题

javadoc of ArrayList下对集合进行迭代修改时,这被认为是编程错误,因此,在“尽力而为”的基础上抛出ConcurrentModificationException

但是随后的问题是,在上面的代码中,我们实际上是在迭代集合时还是在此之前结束了对集合的修改?

流不应该很懒吗?

在进一步搜索此类预期行为时,发现了一些类似的报告并已修复为错误-similar guidelines,,而Java-9已修复该问题。

与此相关的另一个错误-ArrayList.subList().spliterator() is not late-binding

Java-11 [已修复]

尽管根据错误报告已在Java-9中进行了修复,但我正在执行的实际测试是在LTS版本上进行的,并且上面共享的代码可以正常工作。

输出

java-8
subList
java-9

答案 2 :(得分:3)

获取此异常的一种方法是在从基础列表创建流之后更新基础列表,这就是这里发生的情况。

只需在致电add之前执行所有stream()通话,就可以了:

List<String> test = new ArrayList<>();
test.add("A");
test.add("B");
test.add("c");
test.add("d"); // Moved here
test = test.subList(0, 2);
Stream s = test.stream();
// test.add("d") removed here
s.forEach(System.out::println);

答案 3 :(得分:3)

问题是您在使用流(未关闭)时修改了ArrayList的内容。

    Stream s = test.stream();
    test.add("d"); <<<- here
    s.forEach(System.out::println);

如ConcurrentModificationException的javadoc所述:

  

例如,通常不允许一个线程修改一个       在另一个线程对其进行迭代时进行收集。

对于流也是如此,因为在简单情况下,它们基于迭代器。

另外,

  

请注意,此异常并不总是表示对象已被       同时由另一个线程修改。