简体   繁体   English

java.util.ConcurrentModificationException流

[英]java.util.ConcurrentModificationException Streams

I was trying the following code Java 8 SE I ran it directly from eclipse, it has the below-mentioned exception also I ran it with command prompt it produces the same result. 我正在尝试以下代码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);

I am not sure as to why exactly it gives the following exception 我不确定为什么它会给出以下异常

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 version I am running with 我运行的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)

This is because of subList ,let me explain with different scenarios 这是因为subList ,让我用不同的场景来解释

From java docs docs 来自java docs 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. 除了escape-hatch操作iterator()和spliterator()之外,执行在调用终端操作时开始,并在终端操作完成时结束。

Case 1: Successful (because the source can be modified before the terminal operation commences) 情况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);

Output : 输出:

A
B
c
d

Case 2: Failed for sublist different reference 案例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);

Output : 输出:

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)

case 3: Failed for sublist same reference, Both lists are different 情况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);

Output : 输出:

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)

Final Conclusion 定论

The semantics of the list returned by this method become undefined if the backing list (ie, 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. ) (结构修改是那些改变了这个列表的大小,或以其他方式扰乱它的方式,正在进行的迭代可能会产生不正确的结果。

From docs subList docs 来自docs subList docs

Minimal Code 最小代码

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 [Bug] Java-8 [错误]

The code above executed with Java-8 throws a CME. 上面用Java-8执行的代码抛出一个CME。 According to the javadoc of ArrayList 根据ArrayListjavadoc

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 . 此类的iterator和listIterator方法返回的迭代器是快速失败的 :如果在创建迭代器之后的任何时候对列表进行结构修改,除了通过迭代器自己的remove或add方法之外,迭代器将抛出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. 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒着任意的,非确定性行为的风险。

Output : 输出

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

Question

Under the similar guidelines, modifying a collection while it's being iterated is considered to be a programming error and consequently throwing of ConcurrentModificationException is performed on a "best-effort" basis. 类似的指导下,在迭代时修改集合被认为是编程错误,因此抛出ConcurrentModificationException是在“尽力而为”的基础上执行的。

But then the question was, in the code above did we actually end up modifying the collection while it was being iterated or rather before that? 但问题是,在上面的代码中,我们实际上是在迭代时或者在此之前最终修改了集合吗?

shouldn't stream be lazy? 不应该流懒惰?

On searching further for such expected behavior, found something similar reported and fixed as a bug - ArrayList.subList().spliterator() is not late-binding and this has been fixed with Java-9. 在进一步搜索这种预期的行为时,发现类似的报告并修复为一个bug - ArrayList.subList()。spliterator()不是后期绑定 ,这已经用Java-9修复。

Another bug related to this - ArrayList.subList().iterator().forEachRemaining() off-by-one-error 另一个与此相关的错误 - ArrayList.subList()。iterator()。forEachRemaining()off-by-one-error

Java-11 [Fixed] Java-11 [修复]

Though fixed in Java-9 according to the bug report, the actual test that I was performing was on the LTS version and the code as shared above works without any exception. 虽然根据错误报告修复了Java-9,但我执行的实际测试是在LTS版本上,上面共享的代码没有任何异常。

Output : 输出

 java-8 subList java-9 

One way to get this exception is by updating the underlying list after creating a stream from it, which is what happened here. 获得此异常的一种方法是在从中创建流后更新基础列表,这就是此处发生的情况。

Just perform all your add calls before calling stream() , and you should be fine: 只需在调用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);

The problem is that you modify the content of the ArrayList while stream is in use (not closed). 问题是您在使用流(未关闭)时修改ArrayList的内容。

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

As javadoc of ConcurrentModificationException mentions: 由于ConcurrentModificationException的javadoc提到:

For example, it is not generally permissible for one thread to modify a Collection while another thread is iterating over it. 例如,一个线程通常不允许修改Collection而另一个线程正在迭代它。

This is true for the streams as well, as in simple case they are based on iterators. 对于流也是如此,因为在简单的情况下它们基于迭代器。

Additionally, 另外,

Note that this exception does not always indicate that an object has been concurrently modified by a different thread. 请注意,此异常并不总是表示某个对象已被另一个线程同时修改。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM