When I run the following code
List<Integer> list = IntStream.range(0,10).boxed().collect(Collectors.toList());
list.stream().forEach(i -> {
System.out.println("i:" +i);
if (i==5) {
System.out.println("..adding 22");
list.add(22);
}
});
I get the following output:
i:0
i:1
i:2
i:3
i:4
i:5
..adding 22
i:6
i:7
i:8
i:9
Exception in thread "main" java.util.ConcurrentModificationException
Why is the code progressing beyond index 5? I would not have expected the following lines in the output:
i:6
i:7
i:8
i:9
I am missing something here, regarding the behaviour perhaps of forEach. The documentation does state "The behavior of this operation is explicitly nondeterministic." and goes on to talk about parallel streams. I would expect parallel streams to execute forEach in any order whatsoever, but surely serial streams execute the Consumer fed to forEach serially? And if so, why is Java allowing the code to progress beyond the exception generated at index 5? There's one thread here, correct?
Thank you in advance.
Edit: Thank you for answers so far. To be absolutely clear my point is that if I do this:
for(int i: list){
System.out.println("i:" +i);
if(i==5) {
System.out.println("..adding 22");
list.add(22);
}
}
I get this:
i:0
i:1
i:2
i:3
i:4
i:5
..adding 22
Exception in thread "main" java.util.ConcurrentModificationException
but I do not get that in a forEach. So it seems a serial stream forEach is NOT analagous to an iterator, either a hand cranked (Iterator iter = list.iterator...) or an enhanced for loop iterator. This was unexpected to me. But it seems from answers given that this is for "performance reasons". But still it's...unexpected. Just for kicks I tried it with a list of 1m elements:
List<Integer> list = IntStream.range(0,1000000).boxed().collect(Collectors.toList());
list.stream().forEach(
i -> {
if(i%250000==0)
System.out.println("i:" +i);
if(i>999997)
System.out.println("i:" +i);
if(i==5) {
System.out.println("..adding 22");
list.add(22);
}}
);
And I got the (now expected) following output:
i:0
..adding 22
i:250000
i:500000
i:750000
i:999998
i:999999
Exception in thread "main" java.util.ConcurrentModificationException
So as been said, the check it seems is done at the end.
How do you fix Java's ConcurrentModificationException? There are two basic approaches: Do not make any changes to a collection while an Iterator loops through it. If you can't stop the underlying collection from being modified during iteration, create a clone of the target data structure and iterate through the clone.
What Causes ConcurrentModificationException. The ConcurrentModificationException generally occurs when working with Java Collections. The Collection classes in Java are very fail-fast and if they are attempted to be modified while a thread is iterating over it, a ConcurrentModificationException is thrown.
ConcurrentHashMap does not throw ConcurrentModificationException if the underlying collection is modified during an iteration is in progress. Iterators may not reflect the exact state of the collection if it is being modified concurrently. It may reflect the state when it was created and at some moment later.
Since Iterator of HashMap is fail-fast it will throw ConcurrentModificationException if you try to remove entry using Map.
The behaviour you're seeing is specific to the ArrayListSpliterator
that is used by a Stream
over an ArrayList
.
A comment in the code explains the implementation choice:
We perform only a single
ConcurrentModificationException
check at the end offorEach
(the most performance-sensitive method) [JDK 8 source code]
This is consistent with the contract of concurrent-modification checking. Iterators aren't required to fail fast in the case of modifications. If they do choose to fail fast the implementors can decide how strictly to implement the checks. For instance, there's often a trade-off to be made between correctness and performance, as indicated above.
This exception may be thrown by methods that have detected concurrent modification of an object. [...] Fail-fast operations throw
ConcurrentModificationException
on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness:ConcurrentModificationException
should be used only to detect bugs. [Java SE 8 API docs]
Well, there is a comment under ArrayListSpliterator
:
If ArrayLists were immutable, or structurally immutable (no adds, removes, etc), we could implement their spliterators with Arrays.spliterator. Instead we detect as much interference during traversal as practical without sacrificing much performance
So the check for when the interference happens on the source List
is not done (probably, I haven't looked too much into the implementation) per element basis, but at some other time, the main goal would be to not sacrifice performance, but still fail when the source is "edited".
You are still violating non-interference
and your code will still fail, later, rather than sooner in this case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With