Back to Parallel Streams
Q7Question 7 of 10

Convert a stream to a parallel stream and sum all salaries, explaining when parallelism helps vs hurts

Key Concepts
  • Call parallelStream() or stream().parallel() to convert to parallel execution
  • Parallel streams use ForkJoinPool.commonPool() by default, splitting work across available cores
  • Summing is associative, so parallel partial sums combine correctly regardless of split order
  • Parallelism helps for large datasets (100K+ elements) with CPU-bound operations on splittable sources
  • Parallelism hurts for small datasets where split/merge overhead dominates the actual work
  • Never do blocking I/O in a parallel stream because it starves the shared common pool

Definition

Parallel sum helps for large data and expensive work, but hurts for small data due to the fixed cost of splitting, scheduling, and merging.
  • 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?

JAVA
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);
    }
}
TEXT
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. ArrayList and arrays split in O(1) by index. LinkedList splits 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.

JAVA
// 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.

JAVA
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.

JAVA
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 BigDecimal to 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: ArrayList and arrays split well, LinkedList does not.

Visual Overview

DIAGRAM
  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.

---

Share
Use ← → arrow keys to navigate