How I Can Understand the Basics of ExecutorService and the Fork/Join Framework

Jacky - Oct 12 '23 - - Dev Community

Java is a popular programming language that offers robust support for concurrency, making it an excellent choice for developing multi-threaded applications. Two key components in Java that simplify the process of managing and executing tasks concurrently are the ExecutorService and Fork/Join Framework. In this article, we’ll explore these frameworks and explain them in a simple and beginner-friendly manner.

ExecutorService

ExecutorService is like the manager of a group of workers (threads) that helps you execute tasks concurrently in a controlled manner. It abstracts away much of the complexity involved in managing threads. Let’s dive into a simple example to illustrate its usage.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorServiceExample {
    public static void main(String[] args) {
        // Create an ExecutorService with a fixed pool of 4 threads
        ExecutorService executorService = Executors.newFixedThreadPool(4);

        // Submit tasks for execution
        for (int i = 0; i < 10; i++) {
            final int taskNumber = i;
            executorService.execute(() -> {
                System.out.println("Task " + taskNumber + " is running on thread " + Thread.currentThread().getName());
            });
        }

        // Shutdown the ExecutorService when done
        executorService.shutdown();
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, we:

1 .Create an ExecutorService with a fixed pool of 4 threads using Executors.newFixedThreadPool(4).

  1. Submit 10 tasks for execution using the execute method.
  2. The ExecutorService manages the allocation and reuse of threads for these tasks.
  3. We shut down the ExecutorService once we're done with it.

Fork/Join Framework

Fork/Join Framework

The Fork/Join Framework is a more specialized tool designed for parallel processing. It is ideal for situations where you need to divide a large task into smaller sub-tasks and then combine their results. Let’s understand it with a classic example — calculating the sum of elements in an array.

import java.util.concurrent.RecursiveTask;
import java.util.concurrent.ForkJoinPool;

public class ForkJoinExample {
    public static void main(String[] args) {
        int[] data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        ForkJoinPool pool = new ForkJoinPool();

        int result = pool.invoke(new SumTask(data, 0, data.length));
        System.out.println("Sum: " + result);
    }
}

class SumTask extends RecursiveTask<Integer> {
    private int[] data;
    private int start;
    private int end;

    public SumTask(int[] data, int start, int end) {
        this.data = data;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        if (end - start <= 3) { // Small enough, do the task directly
            int sum = 0;
            for (int i = start; i < end; i++) {
                sum += data[i];
            }
            return sum;
        } else {
            int mid = start + (end - start) / 2;
            SumTask leftTask = new SumTask(data, start, mid);
            SumTask rightTask = new SumTask(data, mid, end);
            leftTask.fork(); // Fork new tasks
            int rightResult = rightTask.compute(); // Compute one part
            int leftResult = leftTask.join(); // Wait for the other part
            return leftResult + rightResult;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, we:

  1. Initialize an array of data. 2 Create a ForkJoinPool, which is the heart of the Fork/Join Framework.
  2. Define a SumTask class that extends RecursiveTask<Integer>. This is where we specify the task to be performed.
  3. The SumTask class breaks down the task of summing the array into smaller sub-tasks and combines the results. The framework handles task distribution and result aggregation for us.

How the Fork/Join Framework Works

The Fork/Join Framework employs a divide-and-conquer strategy to parallelize tasks. Here’s a step-by-step breakdown of how it works:

  1. The main task, in this case, the SumTask, is given a large problem to solve. It checks if the problem size is small enough to be solved directly (in this case, when the difference between start and end is less than or equal to 3).
  2. If the problem size is small, it computes the result directly. In this example, it sums the elements in the array between start and end.
  3. If the problem is too large, the main task splits it into two sub-tasks, leftTask and rightTask, to solve smaller parts of the problem.
  4. The leftTask is then forked, which means it's submitted for parallel execution. This allows the framework to allocate separate threads for each sub-task.
  5. While the rightTask is computed directly, the leftTask is allowed to execute in parallel.
  6. Once both sub-tasks are complete, the leftTask and rightTask results are joined together, effectively combining the sub-task results to obtain the final result.

This divide-and-conquer approach continues recursively until all sub-tasks are small enough to be computed directly, and their results are combined to produce the overall result.

Conclusion

Concurrency in Java is made simpler and more efficient with the ExecutorService and Fork/Join Framework. The ExecutorService manages threads, making it easier to execute tasks concurrently, while the Fork/Join Framework is designed for parallel processing, allowing you to divide and conquer complex tasks.

These tools are valuable additions to a Java developer’s toolbox, enabling the efficient use of multiple cores and improving application performance. By mastering these frameworks, you can build high-performance, multi-threaded Java applications with ease.

Whether you’re working on a multi-threaded server application, a data processing pipeline, or any other project that requires concurrency, understanding these concepts will undoubtedly help you make the most of Java’s powerful capabilities.

Happy coding and happy concurrent programming! 🚀👨‍💻👩‍💻

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .