java.util.ConcurrentModificationException 流
java.util.ConcurrentModificationException Streams
我正在尝试以下代码 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)
获得此异常的一种方法是在从基础列表创建流后更新基础列表,这就是此处发生的情况。
只需在调用 stream()
之前执行所有 add
调用,就可以了:
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);
问题是您在流正在使用(未关闭)时修改 ArrayList 的内容。
Stream s = test.stream();
test.add("d"); <<<- here
s.forEach(System.out::println);
正如 ConcurrentModificationException 的 javadoc 提到的:
For example, it is not generally permissible for one thread to modify a
Collection while another thread is iterating over it.
对于流也是如此,因为在简单的情况下它们基于迭代器。
此外,
Note that this exception does not always indicate that an object has been
concurrently modified by a different thread.
这是因为subList
,我分情况说明一下
来自 java 文档 docs
For well-behaved stream sources, the source can be modified before the terminal operation commences and those modifications will be reflected in the covered elements.
Except for the escape-hatch operations iterator() and spliterator(), execution begins when the terminal operation is invoked, and ends when the terminal operation completes.
案例一:成功(因为可以在终端操作开始前修改源)
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)
最终结论
The semantics of the list returned by this method become undefined if the backing list (i.e., this list) is structurally modified in any way other than via the returned list. (Structural modifications are those that change the size of this list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.)
来自文档子列表 docs
最小代码
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。根据 javadoc of ArrayList
The iterators returned by this class's iterator and listIterator
methods are fail-fast: if the list is structurally modified at any
time after the iterator is created, in any way except through the
iterator's own remove or add methods, the iterator will throw a
ConcurrentModificationException
.
Thus, in the face of concurrent
modification, the iterator fails quickly and cleanly, rather than
risking arbitrary, non-deterministic behavior at an undetermined time
in the future.
输出:
java-8
subList
Exception in thread "main" java.util.ConcurrentModificationException
问题
在 similar guidelines, 下,在迭代时修改集合被认为是编程错误,因此 ConcurrentModificationException
的抛出是在 "best-effort" 的基础上执行的。
但接下来的问题是,在上面的代码中,我们实际上是在集合被迭代时或更确切地说之前修改了集合吗?
流不应该偷懒吗?
在进一步搜索此类预期行为时,发现了一些类似的错误报告和修复 - ArrayList.subList().spliterator() is not late-binding 并且已通过 Java-9 修复。
另一个与此相关的错误 - ArrayList.subList().iterator().forEachRemaining() off-by-one-error
Java-11 [固定]
虽然根据错误报告在 Java-9 中进行了修复,但我执行的实际测试是在 LTS 版本上进行的,上面共享的代码无一例外地工作。
输出:
java-8
subList
java-9
我正在尝试以下代码 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)
获得此异常的一种方法是在从基础列表创建流后更新基础列表,这就是此处发生的情况。
只需在调用 stream()
之前执行所有 add
调用,就可以了:
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);
问题是您在流正在使用(未关闭)时修改 ArrayList 的内容。
Stream s = test.stream();
test.add("d"); <<<- here
s.forEach(System.out::println);
正如 ConcurrentModificationException 的 javadoc 提到的:
For example, it is not generally permissible for one thread to modify a Collection while another thread is iterating over it.
对于流也是如此,因为在简单的情况下它们基于迭代器。
此外,
Note that this exception does not always indicate that an object has been concurrently modified by a different thread.
这是因为subList
,我分情况说明一下
来自 java 文档 docs
For well-behaved stream sources, the source can be modified before the terminal operation commences and those modifications will be reflected in the covered elements.
Except for the escape-hatch operations iterator() and spliterator(), execution begins when the terminal operation is invoked, and ends when the terminal operation completes.
案例一:成功(因为可以在终端操作开始前修改源)
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)
最终结论
The semantics of the list returned by this method become undefined if the backing list (i.e., this list) is structurally modified in any way other than via the returned list. (Structural modifications are those that change the size of this list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.)
来自文档子列表 docs
最小代码
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。根据 javadoc of ArrayList
The iterators returned by this class's iterator and listIterator methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a
ConcurrentModificationException
.Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.
输出:
java-8 subList Exception in thread "main" java.util.ConcurrentModificationException
问题
在 similar guidelines, 下,在迭代时修改集合被认为是编程错误,因此 ConcurrentModificationException
的抛出是在 "best-effort" 的基础上执行的。
但接下来的问题是,在上面的代码中,我们实际上是在集合被迭代时或更确切地说之前修改了集合吗?
流不应该偷懒吗?
在进一步搜索此类预期行为时,发现了一些类似的错误报告和修复 - ArrayList.subList().spliterator() is not late-binding 并且已通过 Java-9 修复。
另一个与此相关的错误 - ArrayList.subList().iterator().forEachRemaining() off-by-one-error
Java-11 [固定]
虽然根据错误报告在 Java-9 中进行了修复,但我执行的实际测试是在 LTS 版本上进行的,上面共享的代码无一例外地工作。
输出:
java-8 subList java-9