Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen a lot of developers treat the .parallel() method or parallelStream() as a "turbo button." The logic seems sound: "I have a 16-core processor, and I'm only using one core for this stream, so if I make it parallel, it should be roughly 16 times faster."

The "Turbo Button" Fallacy

Here is the reality: adding parallelism often makes your code slower, especially for small datasets or simple operations. I once reviewed a PR where a dev parallelized a stream that was processing a list of about 500 strings to filter out nulls. The parallel version was nearly twice as slow as the sequential one.

Why? Because parallelism isn't free. Java has to split the source data into chunks (using a Spliterator), assign those chunks to different threads in the ForkJoinPool, and then merge the results back together at the end. If the work being done inside the stream is trivial—like a simple filter or a basic sum—the overhead of managing the threads completely dwarfs the time saved by distributing the work.

// This is likely SLOWER than a sequential stream
List<Integer> numbers = IntStream.range(0, 1000).boxed().collect(Collectors.toList());
long sum = numbers.parallelStream()
                  .mapToLong(i -> i * 2)
                  .sum();

When Parallelism Actually Pays Off

To decide if you should go parallel, I use a rough mental model called the N*Q rule. N is the number of elements, and Q is the computational cost per element. You want the product of N*Q to be high.

  • Low N, Low Q: (e.g., summing 100 integers) → Use sequential.
  • High N, Low Q: (e.g., summing 10 million integers) → Parallel might help, but check the data source first.
  • Low N, High Q: (e.g., calling a heavy API for 10 different users) → Parallel is a great candidate.
  • High N, High Q: (e.g., processing 10,000 images) → Definitely use parallel.

But there's a catch: your data source matters. Parallel streams rely on the ability to split the data evenly. An ArrayList is perfect because it's backed by an array; Java knows exactly where the middle is. A LinkedList, however, is a disaster for parallel streams because Java has to traverse the list sequentially just to find the split point, effectively killing any performance gain.

The Common Pool and the Danger of Blocking

Something that isn't always obvious is that all parallel streams in a single JVM share the same ForkJoinPool.commonPool() by default. This is a global resource.

If you use a parallel stream to perform a blocking I/O operation—like making an HTTP request or reading a file—you aren't just slowing down that one stream. You are potentially starving every other parallel stream in your entire application of threads. If you're doing heavy I/O, I strongly suggest avoiding parallel streams and using a dedicated ExecutorService or CompletableFuture instead. I've spent way too many late nights debugging "random" application freezes only to find one parallel stream was clogging the common pool with blocking calls.

Side Effects: The Silent Killer

Finally, never, ever modify shared state inside a parallel stream. Sequential streams are predictable; parallel streams are chaotic. If you're adding elements to a non-thread-safe ArrayList from within a forEach on a parallel stream, you'll get ConcurrentModificationException or, even worse, silent data loss.

// DANGEROUS: This will cause race conditions
List<Integer> results = new ArrayList<>();
list.parallelStream().forEach(i -> results.add(i)); 

If you need to collect results, use .collect(). The Stream API is designed to handle the merging of partial results safely for you.




📋 Practical Task

Performance Benchmark: Processing Large-Scale Transaction Logs

You are tasked with analyzing a simulated list of 1,000,000 transaction records. Each record contains a transaction ID and an amount. You need to calculate the total sum of all transactions that exceed $500.00.

Your Goal: Implement the logic twice—once using a sequential stream and once using a parallel stream. You must wrap both calls in a timing mechanism (using System.currentTimeMillis() or System.nanoTime()) to prove that for a dataset of this size and operation, parallelization provides a performance boost.

Requirements:

  • Generate a List<Transaction> using an ArrayList with 1 million entries.
  • The Transaction class should have a double amount field.
  • Compare the execution time of .stream().filter(...).mapToDouble(...).sum() against .parallelStream().filter(...).mapToDouble(...).sum().
  • Print both the total sum (to ensure they are identical) and the time taken for each approach.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.