When is Common ForkJoinPool Used Internally by JVM?

16 Apr 2024

ForkJoinPool is a special-purpose ExecutorService in Java designed for parallelizing divide-and-conquer algorithms. It is part of the java.util.concurrent package introduced in Java 7. The main idea behind ForkJoinPool is to efficiently utilize available CPU cores by recursively splitting tasks into smaller subtasks and executing them in parallel.

The common pool of ForkJoinPool threads are implicitly used in Java applications whenever ForkJoinTasks are submitted for execution without explicitly specifying a custom ForkJoinPool instance. Here are some scenarios in which the common pool threads are implicitly utilized:

  1. Parallel Stream Operations: When using parallel stream operations in Java 8 or later, such as parallel() or parallelStream(), the underlying implementation may utilize the common pool threads for parallel execution of stream operations. For example:

    javaCopy codeList<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    int sum = numbers.parallelStream().mapToInt(Integer::intValue).sum();
  2. RecursiveTask and RecursiveAction: If you create instances of RecursiveTask or RecursiveAction and invoke their fork() and join() methods, the tasks are typically executed using the common pool threads. For example:

    javaCopy codeclass MyRecursiveTask extends RecursiveTask<Integer> {
        protected Integer compute() {
            // Task logic here
    MyRecursiveTask task = new MyRecursiveTask();
    int result = ForkJoinPool.commonPool().invoke(task);
  3. Task Execution by ForkJoinPool: Any ForkJoinTasks submitted to the ForkJoinPool using methods like invoke(), submit(), or execute() without specifying a custom pool instance will be executed using the common pool threads.

  4. Parallel Array Operations: Operations on parallel arrays using Arrays.parallelSetAll(), Arrays.parallelSort(), etc., may utilize the common pool threads for parallel processing.

  5. CompletableFuture: Operations on CompletableFuture, especially those involving asynchronous computations and combining results using methods like thenCombine() or thenApplyAsync(), may leverage the common pool threads for execution.

In all these scenarios, the common pool threads are implicitly used by the Java runtime to execute tasks in parallel, taking advantage of multi-core processors and maximizing CPU utilization.

Generally, it’s not advisable to use ForkJoinPool threads directly in the methods of CompletableFuture. Here's why:

Reasons to Avoid Direct Usage:

  • Conflicting Execution Models: CompletableFuture uses its own asynchronous execution mechanism, often relying on a cached thread pool or a custom executor you provide. Bypassing this mechanism and introducing ForkJoinPool threads can lead to unexpected behavior and potential thread pool management issues.
  • Limited Control: Using thenAsync allows CompletableFuture to manage thread pool utilization based on the workload. Direct ForkJoinPool usage might not align with this management strategy.
  • Blocking: ForkJoinPool is optimized for CPU-bound tasks with coarse-grained parallelism. Blocking operations within thenAsync tasks can hinder the pool's efficiency.

Recommended Approach:

  • Leverage the internal asynchronous execution mechanism of CompletableFuture. You can configure a custom executor for CompletableFuture if you have specific thread pool requirements, but it's usually not necessary for basic asynchronous tasks.
  • If you have CPU-bound tasks suitable for ForkJoinPool, consider processing them before using CompletableFuture. Utilize CompletableFuture for the asynchronous chaining of these pre-processed results.

In summary, exercise caution while using CompletableFuture methods without passing custom executor service as it might create some unexpected behavior.