Parallel Streams: Performance and Pitfalls
Enable parallel streams, understand the common thread pool, and avoid shared mutable state bugs.
Parallel Streams: Performance and Pitfalls is a free Java Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Enabling Parallel Streams
Call .parallel() on any stream or use Collection.parallelStream(). The stream operations run on the common ForkJoinPool (default: CPU cores - 1 threads).
long count = list.parallelStream()
.filter(n -> n % 2 == 0)
.count();
System.out.println(count);When Parallel Pays Off
Parallel streams are worth it when: the dataset is large (100,000+ elements), each element operation is computationally expensive, and the pipeline is stateless and order-independent.
// Good candidate: CPU-heavy computation on large dataset
long sum = LongStream.rangeClosed(1, 10_000_000)
.parallel()
.filter(n -> isPrime(n))
.sum();
System.out.println(sum);When NOT to Use Parallel Streams
Avoid parallel streams for: small collections, I/O-bound operations (they block ForkJoinPool threads), stateful operations (sorting, distinct), or when order matters and is expensive to restore.
// Bad: I/O bound — blocking ForkJoinPool threads starves other tasks
List<String> result = urls.parallelStream()
.map(url -> httpGet(url)) // BLOCKS the common pool
.collect(Collectors.toList());Shared Mutable State Bug
Parallel streams run operations on multiple threads simultaneously. Modifying a shared mutable collection causes data races and incorrect results.
// RACE CONDITION — never do this:
List<Integer> results = new ArrayList<>(); // not thread-safe
numbers.parallelStream().forEach(n -> results.add(n)); // corrupts list!
// Fix:
List<Integer> safe = numbers.parallelStream().collect(Collectors.toList());Stateful Operations: sorted and distinct
sorted() and distinct() require seeing all elements before producing output, limiting parallelism and often making parallel slower than sequential.
// sorted() forces collect-all, then sort — parallel overhead usually not worth it:
list.parallelStream().sorted().collect(Collectors.toList());Order-Sensitive Operations
findFirst() and forEachOrdered() maintain encounter order in parallel streams, adding synchronization cost. Use findAny() or forEach() when order doesn't matter.
// Faster in parallel (order-insensitive):
Optional<Integer> any = list.parallelStream().filter(n -> n > 10).findAny();
// Slower in parallel (must preserve order):
Optional<Integer> first = list.parallelStream().filter(n -> n > 10).findFirst();Choosing Thread Pool Size
The common ForkJoinPool uses Runtime.getRuntime().availableProcessors() - 1 threads. Run parallel streams on a custom pool by submitting them inside a ForkJoinPool.invoke() call.
ForkJoinPool custom = new ForkJoinPool(8);
long result = custom.submit(
() -> LongStream.rangeClosed(1, 1_000_000).parallel().sum()
).get();Benchmarking Parallel vs Sequential
Always benchmark with JMH on realistic data sizes. Parallel overhead (splitting, thread coordination, combining) is only justified when the computation time dwarfs the overhead.
@Benchmark
public long sequential() { return LongStream.rangeClosed(1,1_000_000).sum(); }
@Benchmark
public long parallel() { return LongStream.rangeClosed(1,1_000_000).parallel().sum(); }Reduction Operations with Parallel
reduce() and collect() are designed to work correctly in parallel when the operations are associative and the identity value is correct.
// Associative reduce — safe in parallel:
int sum = list.parallelStream().reduce(0, Integer::sum);
// Non-associative: subtraction — NOT safe in parallel:
int bad = list.parallelStream().reduce(0, (a, b) -> a - b); // wrong result!Splittability Matters
Parallel streams split the data source using Spliterator. ArrayList and arrays split in O(1); LinkedList and HashSet split poorly, reducing parallel efficiency.
Summary: Parallel Stream Checklist
Before using parallel: (1) large dataset, (2) CPU-bound ops, (3) no shared mutable state, (4) order-insensitive, (5) splittable source (array/ArrayList). When in doubt, benchmark.
Quick Check
What happens when you add to a non-thread-safe collection in a parallel stream forEach?
Recap
Parallel streams use ForkJoinPool. Effective for large, CPU-bound, order-insensitive, stateless pipelines. Never mutate shared state. Benchmark before committing — parallel is often slower for small data.
Frequently asked questions
Is the “Parallel Streams: Performance and Pitfalls” lesson free?
Yes — the full text of “Parallel Streams: Performance and Pitfalls” is free to read here on the web, and the Java Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO.
What will I learn in “Parallel Streams: Performance and Pitfalls”?
Enable parallel streams, understand the common thread pool, and avoid shared mutable state bugs. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Java Academy?
No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Parallel Streams: Performance and Pitfalls” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Java Academy lesson?
Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- flatMap for Nested Collections
- Parallel Streams: Performance and Pitfalls
- Spliterator: Splitting for Parallelism
- Infinite Streams with iterate and generate