Parallel Streams
Use parallel streams for data-parallel computation and understand when they help versus hurt performance.
Overview
Key Points
- •stream().parallel() or collection.parallelStream() enables parallelism
- •sequential() converts a parallel stream back to sequential
- •Parallel streams use ForkJoinPool.commonPool() by default
- •Use a custom ForkJoinPool by submitting the stream operation as a task
- •Operations must be stateless and non-interfering for correct parallel results
- •Reduction operations (reduce, collect) must be associative and non-interfering
- •findFirst() is slower on parallel streams; use findAny() if order does not matter
- •Ordered parallel operations (like limit, sorted, findFirst) may need merging, adding overhead
- •Avoid shared mutable state; use thread-safe collectors instead
- •Good candidates: large arrays, CPU-bound stateless maps, aggregate reductions
Examples
Basic parallel stream and performance comparison
import java.util.stream.LongStream;
public class Main {
static long computeSquareSum(long n, boolean parallel) {
LongStream stream = LongStream.rangeClosed(1, n);
if (parallel) stream = stream.parallel();
return stream.map(i -> i * i).sum();
}
public static void main(String[] args) {
long n = 50_000_000L;
long t1 = System.currentTimeMillis();
long seqResult = computeSquareSum(n, false);
long seqTime = System.currentTimeMillis() - t1;
long t2 = System.currentTimeMillis();
long parResult = computeSquareSum(n, true);
long parTime = System.currentTimeMillis() - t2;
System.out.println("Sequential: " + seqTime + "ms, result=" + seqResult);
System.out.println("Parallel: " + parTime + "ms, result=" + parResult);
System.out.println("Results match: " + (seqResult == parResult));
}
}On a multi-core machine with large n, parallel should be faster. With small n (< 10,000), sequential is usually faster due to overhead. Always benchmark with your actual data size.
Thread safety: using a thread-safe collector
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.*;
public class Main {
public static void main(String[] args) {
List<String> words = List.of("alpha", "beta", "gamma", "alpha", "beta", "delta");
// WRONG: mutable shared HashMap is not thread-safe
// Map<String, Long> bad = new HashMap<>();
// words.parallelStream().forEach(w -> bad.merge(w, 1L, Long::sum)); // race condition
// CORRECT: use a thread-safe collector
Map<String, Long> freq = words.parallelStream()
.collect(Collectors.groupingByConcurrent(w -> w, Collectors.counting()));
System.out.println(freq);
}
}groupingByConcurrent is the parallel-friendly version of groupingBy. It uses ConcurrentHashMap internally. Regular groupingBy also works in parallel streams but merges results per chunk, which is safe but does extra work.
Custom thread pool for isolated parallel computation
import java.util.concurrent.ForkJoinPool;
import java.util.stream.LongStream;
public class Main {
public static void main(String[] args) throws Exception {
ForkJoinPool pool = new ForkJoinPool(4); // 4-thread pool
long result = pool.submit(() ->
LongStream.rangeClosed(1, 10_000_000)
.parallel()
.sum()
).get();
pool.shutdown();
System.out.println("Sum: " + result);
}
}Submitting to a custom ForkJoinPool ensures parallel stream work runs in that pool instead of the shared common pool. Use this to isolate CPU-intensive tasks from other application components using the common pool.
Common Mistakes
- Using parallel streams for small data sets. The overhead of splitting, scheduling, and merging results is larger than the computation itself for small inputs. Benchmark first.
- Using parallel streams with blocking I/O. Blocking a ForkJoin thread stalls other tasks. Use async I/O or ensure ForkJoin managed blocking via ForkJoinPool.managedBlock().
- Mutating shared state inside parallel stream operations. HashMap is not thread-safe; multiple threads updating it concurrently causes data corruption. Use thread-safe collectors.
- Expecting findFirst() to be fast on parallel streams. findFirst() requires the first element in encounter order, which may not be the first found. Use findAny() if order does not matter.
Note
A rough rule: parallel streams are worth considering for CPU-bound operations on collections with 10,000+ elements. For fewer elements, sequential is usually faster. Always profile in your actual runtime environment rather than assuming parallel is better.
Exercises
Write Java code and click Run to execute it instantly. When your output looks correct, mark as done.
Completed: 0 / 2 exercises
Knowledge Check
Question 1 of 4