Reputation: 1901
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.
Upvotes: 6
Views: 3234
Reputation: 9944
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]
Upvotes: 10
Reputation: 120848
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.
Upvotes: 6
Reputation: 79
Update your code: Use CopyOnWriteArrayList
List<Integer> list = new CopyOnWriteArrayList<>(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);
}
});
Upvotes: -1