📘 Premium Read: Access my best content on Medium member-only articles — deep dives into Java, Spring Boot, Microservices, backend architecture, interview preparation, career advice, and industry-standard best practices.
✅ Some premium posts are free to read — no account needed. Follow me on Medium to stay updated and support my writing.
🎓 Top 10 Udemy Courses (Huge Discount): Explore My Udemy Courses — Learn through real-time, project-based development.
▶️ Subscribe to My YouTube Channel (172K+ subscribers): Java Guides on YouTube
Java Streams make it easy to process data collections using a clean, functional style. But once you discover .parallelStream()
, it’s tempting to switch everything to parallel and expect a performance boost.
Spoiler: parallel streams aren’t always faster — and using them blindly can lead to bugs or even worse performance.
In this article, we’ll explore the differences between parallel and sequential streams, with code examples, performance comparisons, and clear guidelines on when to use which.
🔁 What Are Sequential and Parallel Streams?
- Sequential Stream: Processes elements one at a time, in order, on a single thread.
- Parallel Stream: Splits elements into chunks and processes them simultaneously using the ForkJoinPool.
🧪 Example: Sequential vs Parallel
Let’s run a simple stream operation both ways:
List<Integer> numbers = IntStream.rangeClosed(1, 10_000_000)
.boxed()
.collect(Collectors.toList());
✅ Sequential
long start = System.currentTimeMillis();
long sum = numbers.stream()
.mapToLong(n -> n * 2)
.sum();
System.out.println("Sequential sum: " + sum);
System.out.println("Time: " + (System.currentTimeMillis() - start) + "ms");
⚡ Parallel
long start = System.currentTimeMillis();
long sum = numbers.parallelStream()
.mapToLong(n -> n * 2)
.sum();
System.out.println("Parallel sum: " + sum);
System.out.println("Time: " + (System.currentTimeMillis() - start) + "ms");
🧾 Output (on a multi-core CPU):
Sequential sum: 100000100000000
Time: 42ms
Parallel sum: 100000100000000
Time: 18ms
✅ Parallel stream is faster here — but only because:
- It’s CPU-bound
- It’s stateless
- The collection is large enough
⚠️ When Parallel Streams May Hurt Performance
- Small datasets: Overhead of thread management exceeds the gain.
- IO-bound tasks: Threads spend time waiting — better handled by async IO.
- Stateful operations: Parallel streams don't guarantee order.
- Synchronized blocks: Lead to contention and poor performance.
- UI or request-thread environments: Not safe in web apps unless managed carefully.
🧱 Real-World Example: Heavy Computation
✅ Good Use Case: CPU-heavy Task
List<Integer> inputs = IntStream.rangeClosed(1, 1_000_000).boxed().toList();
long result = inputs.parallelStream()
.mapToLong(JavaStreamExamples::heavyOperation)
.sum();
public static long heavyOperation(int n) {
return LongStream.rangeClosed(1, 1000).reduce(1, (a, b) -> a + b * n);
}
📈 This shows significant gain with parallelStream()
on multi-core CPUs.
❌ Bad Use Case: Mutating Shared State
List<String> names = List.of("Amit", "Ravi", "Neha", "Ritu");
List<String> result = new ArrayList<>();
names.parallelStream()
.forEach(name -> result.add(name.toUpperCase())); // 🚨 Race condition!
❌ This may throw ConcurrentModificationException
or produce inconsistent results.
✅ Fix:
List<String> result = names.parallelStream()
.map(String::toUpperCase)
.collect(Collectors.toList());
🧠 Key Differences (with Table)
Feature | Sequential Stream | Parallel Stream |
---|---|---|
Threading | Single-threaded | Multi-threaded (ForkJoinPool) |
Order maintained | Yes | Not guaranteed |
Best for | Small or IO-bound tasks | Large, CPU-bound operations |
Overhead | Low | Higher (splitting, merging) |
Performance on small data | Consistent | Can be slower |
✅ When to Use Parallel Streams
- Dataset is large (e.g. millions of records)
- Processing is CPU-intensive
- Operations are stateless and independent
- You don’t need guaranteed order
- You’re in a multi-core environment (8+ cores benefits more)
❌ When to Avoid Parallel Streams
- Small collections (<1000 items)
- Code involves shared mutable state
- You need strict ordering (e.g. writing to file/database in order)
- Inside web request handling (due to limited thread pools)
- Processing involves I/O latency (use
CompletableFuture
or reactive streams instead)
💡 Best Practices
- Use
ForkJoinPool.commonPool()
wisely. It’s shared across the JVM — don’t overload it. - Profile your stream operations using tools like JMH or System.nanoTime().
- Prefer
.stream()
by default. Reach for.parallelStream()
only after measuring performance.
🔚 Final Thoughts
Java parallel streams are powerful, but only when used correctly. They shine in the right context — and can backfire if used blindly.
Rule of thumb: Use sequential streams unless you have benchmarked a parallel version and confirmed that it helps.
Comments
Post a Comment
Leave Comment