Reputation: 11121
Premise: I've already read this question and others, but I need some clarifications.
I understand that Stream.forEach
method makes the difference (not only) when dealing with parallel streams, and this explains why this
//1
Stream.of("one ","two ","three ","four ","five ","six ")
.parallel()
.forEachOrdered(item -> System.out.print(item));
prints
one two three four five six
But when it comes to intermediate operations, the order is not guaranteed anymore when stream is parallelized. So this code
//2
Stream.of("one ","two ","three ","four ","five ","six ")
.parallel()
.peek(item -> System.out.print(item))
.forEachOrdered(item -> System.out.print(""));
will print something like
four six five one three two
Is it correct to say that forEachOrdered
method only affects order of elements in its own execution? Intuitively, I'm thinking of //1
example being exactly the same as
//3
Stream.of("one ","two ","three ","four ","five ","six ")
.parallel()
.peek(item -> System.out.print("")) //or any other intermediate operation
.sequential()
.forEach(item -> System.out.print(item));
Is my intuition wrong? Am I missing something about the whole mechanism?
Upvotes: 3
Views: 2935
Reputation: 298123
You are right in that the guarantees made for the action of forEachOrdered
only apply to that action and nothing else. But it’s wrong to assume that this is the same as .sequential().forEach(…)
.
sequential
will turn the entire stream pipeline into sequential mode, thus, the action passed to forEach
will be executed by the same thread, but also the preceding peek
’s action. For most intermediate operations, the exact placement of parallel
or sequential
is irrelevant and specifying both makes no sense as only the last one will be relevant.
Also, there is still no guaranty made about the ordering when using forEach
, even if it hasn’t any consequences in the current implementation. This is discussed in “Does Stream.forEach respect the encounter order of sequential streams?”
The documentation of Stream.forEachOrdered
states:
This operation processes the elements one at a time, in encounter order if one exists. Performing the action for one element happens-before performing the action for subsequent elements, but for any given element, the action may be performed in whatever thread the library chooses.
So the action may get invoked by different threads, as perceivable by Thread.currentThread()
but not run concurrently.
Further, if the stream has an encounter order, it will get reconstituted at this place. This answer sheds some light one the difference of encounter order and processing order.
Upvotes: 5
Reputation: 100169
The #3 code is not conceptually equivalent to #1, because parallel()
or sequential()
calls affect the whole stream, not just the subsequent operations. So in #3 case the whole procedure will be performed sequentially. Actually #3 case resembles the early design of the Stream API when you actually could change the parallel/sequential mode. This was considered to be unnecessary complication (and actually added problems, see, for example, this discussion) as usually you only need to change mode to make the terminal operation ordered (but not necessarily sequential). So forEachOrdered()
was added and parallel()/sequential()
semantics was changed to affect the whole stream (see this changeset).
Basically you're right: in parallel stream there's no order guarantee for intermediate operations. If you need to perform them in particular order, you have to use the sequential stream.
Upvotes: 4