Java 26: Parallel Streams — When They Help
easy⏱ 5 mincoursejava
How parallel works
The stream is split via the spliterator into ranges. Each range is processed by a ForkJoinPool.commonPool() worker (by default N = cores - 1). Results are combined via the collector's combiner. Splitting, scheduling, and combining all add overhead — if the per-element work is trivial, overhead dwarfs any gain.
When it helps
(1) The pipeline is stateless (no shared mutable state). (2) The per-element work is non-trivial (real CPU time). (3) The source splits cheaply (ArrayList, int[] — good; LinkedList, HashSet — bad). (4) The collection is large (10k+ elements). All four should hold.
When it hurts
I/O-bound work (blocks pool workers, starves others). Tiny collections (overhead >> work). Stateful ops (sorted/distinct need full buffer). Shared mutable state (race conditions — list.add in a parallel forEach is a bug). Order-dependent ops (forces sequential merge).
Benchmark sequential vs parallel
For arrays of size 10, 1_000, 100_000: run a CPU-heavy op (compute Math.sqrt 1000x per element) sequentially and 'in parallel' (simulated by chunking and Promise.all). Log wall time for each. You should see parallel lose on 10, break-even around 1000, and win big at 100000.
Custom pool for parallel
By default everything shares ForkJoinPool.commonPool(). Mixing I/O with CPU work on the same pool is trouble. Isolate by submitting to a custom pool: myPool.submit(() -> list.parallelStream().reduce(...)).get();.
Quiz: Why is shared mutable state a bug?
list.parallelStream().forEach(list::add) — why dangerous? ArrayList is not thread-safe. Concurrent writes produce data races, corrupt internal state, and throw ArrayIndexOutOfBoundsException. Use .collect(...) to build a new collection safely.