Consider a List<String> stringList
which can be printed in many ways using Java 8 constructs:
stringList.forEach(System.out::println); // 1) Iterable.forEach
stringList.stream().forEach(System.out::println); // 2) Stream.forEach (order maintained generally but doc does not guarantee)
stringList.stream().forEachOrdered(System.out::println); // 3) Stream.forEachOrdered (order maintained always)
stringList.parallelStream().forEach(System.out::println); // 4) Parallel version of Stream.forEach (order not maintained)
stringList.parallelStream().forEachOrdered(System.out::println); // 5) Parallel version ofStream.forEachOrdered (order maintained always)
How are these approaches different from each other?
First Approach (Iterable.forEach
)-
The iterator of the collection is generally used and that is designed to be fail-fast which means it will throw ConcurrentModificationException
if the underlying collection is structurally modified during the iteration. As mentioned in the doc for ArrayList
:
A structural modification is any operation that adds or deletes one or
more elements, or explicitly resizes the backing array; merely setting
the value of an element is not a structural modification.
So it means for ArrayList.forEach
setting the value is allowed without any issue. And in case of concurrent collection e.g. ConcurrentLinkedQueue
the iterator would be weakly-consistent which means the actions passed in forEach
are allowed to make even structural changes without ConcurrentModificationException
exception being thrown. But here the modifications might or might not be visible in that iteration.
Second Approach (Stream.forEach
)-
The order is undefined. Though it may not occur for sequential streams but the specification does not guarantee it. Also the action is required to be non-interfering in nature. As mentioned in doc:
The behavior of this operation is explicitly nondeterministic. For
parallel stream pipelines, this operation does not guarantee to
respect the encounter order of the stream, as doing so would sacrifice
the benefit of parallelism.
Third Approach (Stream.forEachOrdered
)-
The action would be performed in the encounter order of the stream. So whenever order matters use forEachOrdered
without a second thought. As mentioned in the doc:
Performs an action for each element of this stream, in the encounter
order of the stream if the stream has a defined encounter order.
While iterating over a synchronized collection the first approach would take the collection's lock once and would hold it across all the calls to action method, but in case of streams they use collection's spliterator, which does not lock and relies on the already established rules of non-interference. In case collection backing the stream is modified during iteration a ConcurrentModificationException
would be thrown or inconsistent result may occur.
Fourth Approach (Parallel Stream.forEach
)-
As already mentioned no guarantee to respect the encounter order as expected in case of parallel streams. It is possible that action is performed in different thread for different elements which can never be the case with forEachOrdered
.
Fifth Approach (Parallel Stream.forEachOrdered
)-
The forEachOrdered
will process the elements in the order specified by the source irrespective of the fact whether stream is sequential or parallel. So it makes no sense to use this with parallel streams.