JL_SO
JL_SO

Reputation: 1901

Java stream forEach concurrentModificationException unexpected behaviour

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

Answers (3)

Sam
Sam

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 of forEach (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

Eugene
Eugene

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

Vaibhav Shitole
Vaibhav Shitole

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

Related Questions