All topics
streams
advanced

Parallel Streams

Switch a stream pipeline to run in parallel on the common ForkJoinPool — for CPU-bound work over large data.

list.parallelStream() splits the work across several threads instead of one. It is one word longer than stream(), which is the problem: it looks free, and it often makes things slower.

A buffet line vs many self-serve stations. Faster if the food is ready and there's enough room — but useless if everyone needs to use the same fryer (a shared resource).

Key Concepts

1
Every parallel stream in your application shares one pool of threads. There is a single pool built into the JVM, sized to the number of processor cores minus one.
2
So one slow parallel stream delays every other parallel stream in the process, including ones handling completely unrelated requests. And if your code blocks inside a parallel stream — a database call, an HTTP request — that thread is unavailable to everyone else while it waits.
3
Splitting the work is not free. Dividing the data, scheduling the pieces, and merging the results all cost time whether or not the work is worth dividing.
4
As a rough guide, a few thousand items doing trivial work will be slower in parallel than in sequence. The honest answer to "should this be parallel?" is "measure it", and saying that is stronger than guessing.
5
Some collections split badly. An ArrayList or an array can be divided instantly: take the first half and the second half by index.
ArrayList
6
A LinkedList cannot. To find the middle you have to walk through it. Stream.iterate is worse — each item depends on the previous one, so it cannot be divided at all. Reading lines from a file has the same problem.
LinkedListStream.iterate
7
Running those in parallel pays the splitting cost and gains nothing.
8
The correctness trap is shared state. This looks reasonable and is broken:
9
    List<String> out = new ArrayList<>();
    list.parallelStream().forEach(out::add);   // WRONG
10
ArrayList is not built for several threads. You lose items, or get an exception, unpredictably.
ArrayList
11
    List<String> out = list.parallelStream().collect(toList());   // right
12
collect works because each thread builds its own partial result, and those are merged at the end.
collect
13
Order matters for some operations. findFirst has to respect the original order, so it cannot simply take whichever answer appears first. If order does not matter to you, findAny is the parallel-friendly version.
findFirstfindAny
14
And a reduction has to be associative — (a+b)+c must equal a+(b+c). Subtraction is not, so reduce(0, (a,b) -> a-b) gives different answers depending on how the data was split, silently.
(a+b)+ca+(b+c)reduce(0, (a,b) -> a-b)
15
Inside a web server, the case is weak. The server is already running many requests at once, so all the cores are busy. Parallelising inside one request takes capacity from other requests, and usually makes the slowest responses slower rather than faster.
16
The good uses are batch jobs, command-line tools, and genuinely large computations.
17
What the interviewer is probing.1. "When would you use a parallel stream?" Probing: judgement. Stalls: "For big collections." Moves up: large amounts, real computation per item, a splittable source, no shared state, and not inside a request thread — then measure.
18
2. "Which threads does it use?" Probing: the shared pool. Stalls: "A thread pool." Moves up: one pool shared by the whole JVM, sized for computation, so a blocking call starves everything.
19
3. "Why is it slower on a small list?" Probing: fixed costs. Stalls: "It should not be." *Moves up:* splitting, scheduling and merging cost more than the work itself.
20
4. "forEach adding to an ArrayList. What happens?" Probing: shared state. Stalls: "It is slower." Moves up: lost items or an exception; use collect.
forEachArrayListcollect
21
5. "Can it change the result?" Probing: ordering and associativity. Stalls: "No." Moves up: findFirst versus findAny, and a non-associative reduction giving different answers each run.
findFirstfindAny
22
  When parallel can pay
23
  Favourable                       Unfavourable
  ------------------------------------------------------------------
  N in the hundreds of thousands   small collections
  CPU-bound per element            trivial per-element work
  ArrayList, array, IntStream.range LinkedList, Stream.iterate,
                                    BufferedReader.lines()
  Stateless, associative ops       order-dependent logic
  Batch job or CLI                 inside a web request thread
  No IO in the pipeline            any blocking call
24
  The shared pool
25
    common ForkJoinPool parallelism = Runtime.availableProcessors()-1
    -> every parallel stream in the JVM shares it
    -> a blocking call inside one starves all the others
26
    To isolate:
      ForkJoinPool pool = new ForkJoinPool(4);
      pool.submit(() -> list.parallelStream()...).get();
27
  Broken versus correct accumulation
28
    // WRONG -- ArrayList is not thread-safe
    List<String> out = new ArrayList<>();
    list.parallelStream().forEach(out::add);
    // lost elements, or ArrayIndexOutOfBoundsException
29
    // RIGHT -- per-worker accumulation, merged by the combiner
    List<String> out = list.parallelStream().collect(toList());
30
  Associativity matters
31
    reduce(0, Integer::sum)        associative -> same answer
    reduce(0, (a,b) -> a - b)      NOT associative -> answer depends
                                   on the split, silently