Sobes.tech
Middle+

What happens 'under the hood' when using parallelStream()?

sobes.tech AI

Answer from AI

When calling parallelStream() or .parallel() on a stream, operations on stream elements are executed in parallel using a thread pool. By default, ForkJoinPool.commonPool() is used.

The following occurs:

  1. Splitting: The original data source (e.g., Collection, Array) is divided into several subsets (spliterators). A Spliterator is an object that can efficiently iterate over the elements of the source and split it into smaller parts.

  2. Processing: Each subset is processed separately in one of the threads of the ForkJoinPool. Stream operations (e.g., map, filter, reduce) are applied to the elements of each subset in parallel.

  3. Combining: The results of processing from different threads are collected and combined to produce the final result. This step is especially important for terminal operations that aggregate data (e.g., reduce, collect).

Fork/Join Framework:

ForkJoinPool is based on the Fork/Join concept:

  • Fork: The task is split into smaller subtasks that can be executed in parallel.
  • Join: The results of the subtasks are combined to obtain the result of the original task.

ForkJoinPool efficiently manages threads, minimizing overhead for thread creation/destruction and context switching. It uses a work stealing algorithm, where idle threads take tasks from busy threads, ensuring good resource utilization.

Important aspects:

  • Not all streams are easily parallelizable. Effectiveness depends on the data source (e.g., ArrayList splits well, LinkedList poorly) and the type of operations performed (stateless operations parallelize better than stateful ones).
  • Overhead from parallelization may outweigh benefits for small data volumes or simple operations.
  • The result of a parallel stream may be nondeterministic for some operations if they are not associative and commutative (e.g., certain reduce operations).
  • The order of operation execution in a parallel stream is not guaranteed unless ordered operations like sorted() are used (which may reduce parallelization efficiency).

Example of simple usage:

// Example of using parallelStream for summing
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class ParallelStreamExample {

    public static void main(String[] args) {
        List<Integer> numbers = IntStream.rangeClosed(1, 1000000)
                                       .boxed()
                                       .collect(Collectors.toList());

        long sum = numbers.parallelStream() // Creating a parallel stream
                          .mapToLong(i -> i)     // Mapping to Long for summing
                          .sum();               // Terminal sum operation

        System.out.println("Parallel sum: " + sum);
    }
}