Java Stream vs ParallelStream: A Detailed Comparison with Code Examples
Java Streams, introduced in Java 8, revolutionized the way we process collections of data. Streams allow developers to write concise, readable code to perform aggregate operations like filtering, mapping, and reducing on collections. A major feature of Java Streams is the ability to run operations either sequentially or in parallel. This brings us to two important concepts: Stream (sequential) and ParallelStream (parallel).
Java Stream vs ParallelStream: A Detailed Comparison with Code Examples |
In this blog post, we will explore the differences between Java Stream and ParallelStream, when to use each, and provide clear code examples to illustrate their usage.
What is Java Stream?
A Java Stream is a sequence of elements supporting sequential and parallel aggregate operations. It processes elements one by one in a single thread.
- Sequential Stream processes data >in order< on a single core.
- It is simpler and guarantees the order of execution.
- Suitable for tasks where order matters or tasks are lightweight.
Creating a Sequential Stream
You can create a sequential stream from a collection using .stream()
:
import java.util.Arrays;
import java.util.List;
public class SequentialStreamExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println); // Output: 2 4
}
}
What is Java ParallelStream?
ParallelStream performs operations in parallel using multiple threads, leveraging the multi-core architecture of modern processors.
- The collection is divided into multiple substreams.
- Each substream is processed concurrently on different CPU cores.
- Final result combines these concurrent computations.
- Execution order is not guaranteed.
- Best used for large, CPU-intensive tasks where the processing order is unimportant.
Creating a Parallel Stream
You can create a parallel stream in two ways:
- Convert an existing sequential stream with
.parallel()
:
numbers.stream()
.parallel()
.filter(n -> n % 2 == 0)
.forEach(System.out::println);
- Directly create a parallel stream from a collection:
numbers.parallelStream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println);
Key Differences Between Stream and ParallelStream
Feature | Stream (Sequential) | ParallelStream |
---|---|---|
Execution | Single-threaded, sequential execution | Multi-threaded, parallel execution |
CPU Core Utilization | Uses 1 core | Uses multiple CPU cores |
Order of Output | Maintains order | Order not guaranteed |
Performance | Slower for large or CPU intensive tasks | Faster for large scale/CPU intensive tasks |
Overhead | Low | Higher due to thread management |
Use Case | Ordered, dependent, or lightweight tasks | Independent, unordered, or heavy tasks |
Error Prone | Less prone to concurrency issues | More prone to concurrency issues |
When to Use ParallelStream?
- When processing large datasets.
- When each element can be processed independently.
- When order of processing/output doesn't matter.
- When you want to improve performance by utilizing multiple CPU cores.
Avoid parallel streams if:
- The order of processing matters.
- The task is IO-bound or small/simple.
- You have shared mutable state without proper synchronization.
ParallelStream Code Example
import java.util.Arrays;
import java.util.List;
public class ParallelStreamExample {
public static void main(String[] args) {
List<String> letters = Arrays.asList("a", "b", "c", "d", "e");
letters.parallelStream()
.forEach(System.out::println); // Output order may vary
}
}
Output (order may not be a,b,c,d,e):
c
a
e
b
d
Performance Comparison Example
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class StreamPerformanceExample {
public static void main(String[] args) {
List<Integer> numbers = IntStream.rangeClosed(1, 1_000_000).boxed().collect(Collectors.toList());
// Sequential Stream
long start1 = System.currentTimeMillis();
long sum1 = numbers.stream()
.mapToLong(Integer::longValue)
.sum();
long end1 = System.currentTimeMillis();
System.out.println("Sequential stream sum: " + sum1 + " in " + (end1 - start1) + " ms");
// Parallel Stream
long start2 = System.currentTimeMillis();
long sum2 = numbers.parallelStream()
.mapToLong(Integer::longValue)
.sum();
long end2 = System.currentTimeMillis();
System.out.println("Parallel stream sum: " + sum2 + " in " + (end2 - start2) + " ms");
}
}
This example sums one million integers and you will often see the parallel stream finishing faster, especially on multi-core systems.
Summary
- Java Stream is sequential, easier to debug, and preserves order.
- Java ParallelStream splits tasks across multiple cores for faster execution at the cost of unordered results and potential overhead.
- Choose parallel streams for computationally intensive, independent tasks.
- Use sequential streams when order and simplicity are priorities.
This understanding will help write efficient, multi-core aware Java applications that leverage the power of Java 8+ streams effectively.
This blog post covered a detailed comparison of Java Stream vs ParallelStream with practical code examples and guidance on when to choose each one.
If you want code snippets or explanation on any specific part of the Java Streams, feel free to ask!