我正在尝试以下代码 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)
答案 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执行的代码将引发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-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所述:
例如,通常不允许一个线程修改一个 在另一个线程对其进行迭代时进行收集。
对于流也是如此,因为在简单情况下,它们基于迭代器。
另外,
请注意,此异常并不总是表示对象已被 同时由另一个线程修改。