Dr. Vatsal Shah
Subject Material

Java · Collections · Topic 11

Parallel Streams

Use Java parallel streams responsibly by understanding execution, ordering, workload suitability, measurement, and thread-safe collection.

Topic progress · 1 of 5 sections

Step 1 of 5

11.1 Introduction

Learning objective

Create a parallel stream directly or convert an existing stream.

ParallelStreamDemo.java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class ParallelStreamDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        List<Integer> orderedResult = numbers.parallelStream()
                .map(number -> number * 10)
                .collect(Collectors.toList());

        System.out.println("Ordered result: " + orderedResult);

        long count = numbers.stream()
                .parallel()
                .filter(number -> number >= 3)
                .count();

        System.out.println("Values at least 3: " + count);
    }
}
Output
Ordered result: [10, 20, 30, 40, 50]
Values at least 3: 3

What changes

A parallel stream divides data into smaller parts and may process those parts on multiple CPU cores. The actual threads and scheduling are managed by Java.

Two creation forms
numbers.parallelStream();

numbers.stream().parallel();
Check your understanding: Which two methods shown here create parallel processing?

parallelStream() and parallel().

Step 2 of 5

11.2 Sequential versus Parallel Stream

Learning objective

Compare the behavior and costs of sequential and parallel processing.

Sequential and parallel streams compared
Sequential streamParallel stream
Uses one processing flowMay use multiple worker threads
Usually preserves encounter orderforEach() may not preserve encounter order
Easier to understand and debugMore difficult to debug
Often suitable for small collectionsMay help with large CPU-intensive data
Lower coordination overheadAdds splitting and coordination overhead
SequentialParallelComparisonDemo.java
import java.util.Arrays;
import java.util.List;

public class SequentialParallelComparisonDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);

        int sequentialSum = numbers.stream()
                .mapToInt(Integer::intValue)
                .sum();

        int parallelSum = numbers.parallelStream()
                .mapToInt(Integer::intValue)
                .sum();

        System.out.println("Sequential sum: " + sequentialSum);
        System.out.println("Parallel sum: " + parallelSum);
    }
}
Output
Sequential sum: 150
Parallel sum: 150
Check your understanding: Which stream style is normally easier to understand and debug?

A sequential stream.

Step 3 of 5

11.3 Ordering

Learning objective

Distinguish forEach() from forEachOrdered() on an ordered parallel stream.

ParallelOrderingDemo.java
import java.util.Arrays;
import java.util.List;

public class ParallelOrderingDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        System.out.println("Ordered output:");
        numbers.parallelStream()
                .forEachOrdered(System.out::println);
    }
}
Output
Ordered output:
1
2
3
4
5
Order is not guaranteed here
numbers.parallelStream()
       .forEach(System.out::println);
Check your understanding: Which terminal operation preserves encounter order?

forEachOrdered().

Step 4 of 5

11.4 When Parallel Streams May Be Useful

Learning objective

Evaluate workload characteristics before enabling parallel execution.

Suitability guide
Parallel streams may help whenPrefer sequential streams when
The data set is largeThe collection is small
Each element needs substantial CPU workEach operation is very simple
Operations are independentTasks mainly wait for input or output
No shared mutable data is usedProcessing changes shared state
Strict encounter order is unnecessaryStrict order must be preserved
A benchmark shows improvementPerformance has not been measured

Decision checklist

  • Is the work CPU-bound?
  • Is the data set large enough to offset overhead?
  • Are element operations independent?
  • Is the reduction associative?
  • Can the pipeline avoid shared mutable state?
  • Does measurement show a real improvement?
Check your understanding: Should a small collection with trivial operations automatically use a parallel stream?

No.

Step 5 of 5

11.5 Avoid Shared Mutable Data

Learning objective

Replace unsafe side effects with stream collection operations.

Unsafe shared mutation
List<Integer> result = new ArrayList<>();

numbers.parallelStream()
       .filter(number -> number % 2 == 0)
       .forEach(result::add);
ParallelCollectionDemo.java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class ParallelCollectionDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);

        List<Integer> result = numbers.parallelStream()
                .filter(number -> number % 2 == 0)
                .collect(Collectors.toList());

        System.out.println("Even numbers: " + result);
    }
}
Output
Even numbers: [2, 4, 6, 8]

Why collect() is safer

Collectors combine partial results using stream-aware accumulation rather than exposing one ArrayList to concurrent writes.

Check your understanding: Why is result::add unsafe when result is a shared ArrayList?

Multiple worker threads may modify the non-thread-safe list concurrently.