Java Reference
In-Depth Information
map: 1 Thread: main
forEach: 1 Thread: main
map: 2 Thread: main
forEach: 2 Thread: main
map: 3 Thread: main
forEach: 3 Thread: main
map: 4 Thread: main
forEach: 4 Thread: main
map: 5 Thread: main
forEach: 5 Thread: main
map: 6 Thread: main
forEach: 6 Thread: main
map: 7 Thread: main
forEach: 7 Thread: main
As we can see, sequential streams are simple: they execute in the caller's thread. As much as possible,
they perform the entire stream of operations for an element before moving onto the next. There are,
however, some operations (such as stream.sorted() ) that require processing the entire stream: the stream
can't know if the current element is the smallest until it has consumed the entire stream. Therefore, this
call acts as a barrier: the particular element of the stream cannot move beyond it before the subsequent
elements are processed.
The parallel streams are more interesting. If you have sufficient elements, they will delegate some
of their work into the common ForkJoinPool instance. This is the same thread pool in which our Fork/
Join tasks executed, which shows how nicely those frameworks play together. Some of the work may still
done locally (such as number 132), but in any case, the same thread will execute as far along the stream as
possible (as demonstrated by numbers 32 and 182).
However, you should have absolutely no presumption that elements of the stream are processed in
order, or that they are processed promptly after they are generated - we generated 182 items out of the
stream before we processed our sixth item, number 33. If we had a stream where each element was a
gigabyte in size, we would be in trouble! The stream generation itself can get very far ahead of the processing,
and those elements remain cached in memory until they are processed. Therefore, it is always best if parallel
streams generate very small elements (such as indexes or lines of text), and you build those small elements
into larger elements as part of your stream processing.
Another thing to watch out for is the fact that your Fork/Join tasks will share the common Fork/Join
pool with the stream operations themselves. Therefore, you should never have your stream operation wait
for your Fork/Join task to resolve unless you are using ForkJoinTask.join() or ForkJoinTask.invoke()
- those two methods will cause the current thread to process the task itself if it is not yet started. Any other
dependency on a Fork/Join task resolving, whether directly or indirectly, is potentially setting yourself up for
a deadlock.
However, if you can generate a stream of small elements, if your stream operations do not block for very
long, and you can be slightly careful about your Fork/Join tasks, then the stream parallelism is an easy way
to get significant concurrency gains out of your Java applications. You simply specify what you want to have
happen and let the runtime do the concurrency management and the optimization for you. All of this can be
done with readable code because lambdas make it possible to pass around behavior as simply as data.
Search WWH ::

Custom Search