java.util.ConcurrentModificationException流

Joy*_*eep 5 java concurrentmodification java-8 java-stream

我正在尝试以下代码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);
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

我运行的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)
Run Code Online (Sandbox Code Playgroud)

Nam*_*man 7

最少代码

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
Run Code Online (Sandbox Code Playgroud)

Java-8 [错误]

上面用 Java-8 执行的代码会抛出一个 CME。根据javadocArrayList

此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:如果在创建迭代器后的任何时间以任何方式修改了列表的结构,除了通过迭代器自己的 remove 或 add 方法,迭代器将抛出一个 ConcurrentModificationException.

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

输出

java-8
subList
Exception in thread "main" java.util.ConcurrentModificationException
Run Code Online (Sandbox Code Playgroud)

类似的指导方针下,在迭代时修改集合被认为是一个编程错误,因此ConcurrentModificationException在“尽力而为”的基础上执行抛出。

但接下来的问题是,在上面的代码中,我们是否真的在迭代时或更确切地说之前修改了集合?

流不应该是懒惰的吗?

在进一步搜索此类预期行为时,发现了类似的报告并作为错误修复 - ArrayList.subList().spliterator() 不是后期绑定,这已通过 Java-9 修复。

与此相关的另一个错误 - ArrayList.subList().iterator().forEachRemaining() off-by-one-error

Java-11 [固定]

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

输出

java-8
subList
java-9
Run Code Online (Sandbox Code Playgroud)


Dea*_*ool 5

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

来自 java docs 文档

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

除了转义线操作 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);
Run Code Online (Sandbox Code Playgroud)

输出 :

A
B
c
d
Run Code Online (Sandbox Code Playgroud)

案例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);
Run Code Online (Sandbox Code Playgroud)

输出 :

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)
Run Code Online (Sandbox Code Playgroud)

情况 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);
Run Code Online (Sandbox Code Playgroud)

输出 :

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)
Run Code Online (Sandbox Code Playgroud)

定论

如果支持列表(即,此列表)以除返回列表以外的任何方式在结构上进行了修改,则此方法返回的列表的语义将变为未定义。(结构修改是那些改变这个列表的大小,或者以其他方式扰乱它,以致正在进行的迭代可能会产生不正确的结果。

从文档子列表 文档