Definition
- parallelStream() or stream().parallel() splits work across the common
ForkJoinPool. - Associative reduction like summing produces a correct result regardless of how the work is split.
- Helps when the dataset is large and per-element work is substantial enough to outweigh overhead.
- Hurts for small datasets, cheap operations, or poorly-splitting sources like
LinkedList. - Shared pool means a slow parallel stream can starve other parallel work in the application.
At the National Bank of India, Priya can assign many clerks to each total a portion of the salary ledger and combine their subtotals at the end. That helps when the ledger has a million entries, but for a ten-entry ledger the time spent splitting pages and coordinating clerks costs more than it saves.
Understanding the Concepts
What is a parallel stream?
A parallel stream splits the data source into chunks and processes them concurrently on multiple threads from the common ForkJoinPool. Each chunk is processed independently, and the results are merged at the end. You create one by calling parallelStream() on a collection or stream().parallel() on an existing stream.
How do you sum salaries in parallel?
package com.bank.hr;
import java.math.BigDecimal;
import java.util.List;
public class ParallelSalarySum {
record Employee(String id, String name, String department, BigDecimal salary) {}
public static void main(String[] args) {
List<Employee> staff = List.of(
new Employee("SAV2001", "Rajesh", "TRADING", new BigDecimal("1800000")),
new Employee("SAV2002", "Priya", "RISK", new BigDecimal("1200000")),
new Employee("SAV2003", "Meera", "TRADING", new BigDecimal("1500000")),
new Employee("SAV2004", "Deepak", "RISK", new BigDecimal("900000")),
new Employee("SAV2005", "Amit", "COMPLIANCE", new BigDecimal("1050000"))
);
// Sequential sum
double seqSum = staff.stream()
.mapToDouble(e -> e.salary().doubleValue())
.sum();
// Parallel sum
double parSum = staff.parallelStream()
.mapToDouble(e -> e.salary().doubleValue())
.sum();
System.out.printf("Sequential: Rs.%.0f%n", seqSum);
System.out.printf("Parallel: Rs.%.0f%n", parSum);
// Exact sum with BigDecimal in parallel
BigDecimal exactSum = staff.parallelStream()
.map(Employee::salary)
.reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println("Exact: Rs." + exactSum);
}
}
Sequential: Rs.6450000
Parallel: Rs.6450000
Exact: Rs.6450000
When does parallelism help?
Parallelism helps when all three conditions are met:
- Large dataset: the element count is high enough (typically 100K+ for cheap operations) that per-element work across cores outweighs the fixed overhead.
- CPU-bound operations: each element requires meaningful computation, not just I/O waiting.
- Splittable source: the data structure splits efficiently.
ArrayListand arrays split in O(1) by index.LinkedListsplits by traversal, which is slow.
When does parallelism hurt?
| Scenario | Why it hurts |
|---|---|
| Small dataset (< 1000) | Split/merge overhead exceeds the work |
| Cheap per-element operation | Coordination costs more than the computation |
| LinkedList source | Splitting requires traversal, defeating parallelism |
| Blocking I/O inside lambda | Threads block, starving the shared pool |
| Shared mutable state | Race conditions cause incorrect results |
| Stateful operations (sorted, distinct) | Require cross-thread coordination |
What are common mistakes?
Mistake: Doing blocking I/O in a parallel stream.
// WRONG: blocking I/O starves the common ForkJoinPool
accountIds.parallelStream()
.map(id -> callRemoteService(id)) // blocks thread
.toList();
// CORRECT: use sequential stream for I/O, or a custom ForkJoinPool
accountIds.stream()
.map(id -> callRemoteService(id))
.toList();
How does the ForkJoinPool work with parallel streams?
All parallel streams share the common ForkJoinPool, whose size defaults to Runtime.getRuntime().availableProcessors() - 1. A slow parallel task on one stream can starve another parallel stream running in the same JVM.
To isolate a parallel stream, submit it to a custom pool.
import java.util.concurrent.ForkJoinPool;
ForkJoinPool customPool = new ForkJoinPool(4);
customPool.submit(() ->
staff.parallelStream()
.mapToDouble(e -> e.salary().doubleValue())
.sum()
).get();
customPool.shutdown();
What about BigDecimal precision in parallel?
Parallel double sums can differ slightly from sequential ones because floating-point addition is not strictly associative: (a + b) + c may differ from a + (b + c) by a tiny rounding error. For exact money calculations, use BigDecimal reduction.
BigDecimal exactTotal = staff.parallelStream()
.map(Employee::salary)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// BigDecimal addition is truly associative, so parallel is safe and exact
What should a senior engineer know?
- Parallel streams share the common
ForkJoinPool, so never block inside one. - Measure before parallelizing: the crossover point depends on dataset size, per-element cost, and source splittability.
- For money, use
BigDecimalto avoid floating-point rounding differences between sequential and parallel. - The common pool size is configurable via
-Djava.util.concurrent.ForkJoinPool.common.parallelism=N.
What are common interview follow-up questions?
- "What pool does a parallel stream use?" The common
ForkJoinPool. - "How do you use a custom pool?" Submit the stream operation as a task to a custom
ForkJoinPool. - "Is parallel always faster?" No. Measure with representative data.
- "What makes a source splittable?" Index-based access:
ArrayListand arrays split well,LinkedListdoes not.
Visual Overview
staff.parallelStream().mapToDouble(salary).sum() ┌──────────────────────────────────────────────┐ │ Common ForkJoinPool │ │ │ │ Thread-1: [Rajesh, Priya] → partial = 3.0M │ │ Thread-2: [Meera, Deepak] → partial = 2.4M │ │ Thread-3: [Amit] → partial = 1.05M│ │ │ │ merge: 3.0M + 2.4M + 1.05M │ │ = Rs.6,450,000 │ └──────────────────────────────────────────────┘
Key Takeaway
To sum salaries in parallel, call parallelStream() or .parallel() and then mapToDouble(salary).sum(). The work is split into chunks processed on multiple threads from the common ForkJoinPool, and because addition is associative the partial sums combine into the correct total regardless of how the data was split.
At the National Bank of India, Rajesh only parallelizes the nightly batch over millions of salary records after measuring that it actually wins. For small lists, the fixed cost of splitting, scheduling, and merging exceeds the actual computation, making sequential faster. For large lists with CPU-bound operations on splittable sources like ArrayList, parallel can speed things up significantly.
The critical rule is to never do blocking I/O inside a parallel stream, because all parallel streams share the common pool and blocking starves other work. For exact money calculations, use BigDecimal reduction to avoid floating-point rounding differences between sequential and parallel sums.
Remember one sentence: parallel helps for large data with CPU-bound work on splittable sources, but hurts for small data where split/merge overhead dominates.
---