Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
ForkJoinPool | Multithreading Best Practices
Multithreading in Java
course content

Conteúdo do Curso

Multithreading in Java

Multithreading in Java

1. Multithreading Basics
2. Synchronized Collections
3. High-level Synchronization Mechanisms
4. Multithreading Best Practices

bookForkJoinPool

The ForkJoinPool class in Java for working with the Fork/Join framework is just the realization of this. It provides mechanisms for managing tasks that can be divided into smaller subtasks and executed in parallel.

java

Main

copy
1
ForkJoinPool forkJoinPool = new ForkJoinPool();

ForkJoinPool is based on the concept of two basic actions: fork and join. Fork is an action where we break a large task into several smaller subtasks that can be executed in parallel. Join is the process by which the results of these subtasks are combined together to form the result of the original task.

Case Study

Imagine that you need to analyze a huge data set that can be divided into several smaller sets. If you process each data set separately and then merge the results, you can speed up the processing significantly, especially on multiprocessor systems.

How to use ForkJoinPool

There are 2 classes to implement tasks in them, they are RecursiveTask and RecursiveAction. Both of them require an abstract compute() method to be implemented. In RecursiveTask the compute() method returns a value, while in RecursiveAction the compute() method returns void.

java

Main

copy
12345678
class ExampleTask extends RecursiveTask<String> { @Override protected String compute() { // code return null; } }

When we inherit from a RecursiveTask we must be sure to specify what type of data our compute() method will return using this syntax RecursiveTask<String>.

java

Main

copy
1234567
class ExampleAction extends RecursiveAction { @Override protected void compute() { // code } }

Here it's the opposite, we don't need to specify the type explicitly in RecursiveAction, because our method won't return anything and will just perform the task.

Starting a Task

By the way, we can start a task for execution without even using ForkJoinPool, just using fork() and join() methods.

It is important to note that the fork() method sends the task to some thread. The join() method is used to get the result.

java

Main

copy
12345
public static void main(String[] args) { ExampleTask simpleClass = new ExampleTask(); simpleClass.fork(); System.out.println(simpleClass.join()); }

ForkJoinPool Main Methods

  • invoke(): Starts a task in the pool and waits for it to complete. Returns the result of the task's completion;
  • submit(): Submits a task to the pool, but does not block the current thread while waiting for the task to complete. Can be used to submit tasks and retrieve their Future objects;
  • execute(): Runs a task in the pool, but does not return a result and not block the current thread.

How can we start a task using ForkJoinPool,very simple!

java

Main

copy
12345
public static void main(String[] args) { ExampleAction simpleClass = new ExampleAction(); ForkJoinPool forkJoinPool = new ForkJoinPool(); System.out.println(forkJoinPool.invoke(simpleClass)); }

But what is the difference between running fork(), join() and via the ForkJoinPool class?

It's very simple! When using the first method with the help of fork(), join() methods, we start the task in the same thread in which these methods were called, blocking this thread, while with the help of ForkJoinPool class we are allocated a thread from the pool and it works in this thread without blocking the main thread.

Let's take a closer look, let's assume we have such an implementation:

java

Main

copy
12345678
class ExampleRecursiveTask extends RecursiveTask<String> { @Override protected String compute() { System.out.println("Thread: " + Thread.currentThread().getName()); return "Wow, it works!!!"; } }

And we want to run this code using fork() and join(). Let's see what will be printed to the console and which thread will perform this task.

java

Main

copy
1234567
public class Main { public static void main(String[] args) { ExampleRecursiveTask simpleClass = new ExampleRecursiveTask(); simpleClass.fork(); System.out.println(simpleClass.join()); } }

And we get this output to the console:

Let's now check what happens if we run with ForkJoinPool:

java

Main

copy
1234567
public class Main { public static void main(String[] args) { ExampleRecursiveTask simpleClass = new ExampleRecursiveTask(); ForkJoinPool forkJoinPool = new ForkJoinPool(); System.out.println(forkJoinPool.invoke(simpleClass)); } }

And we get this conclusion:

And as you can see the difference is that using the 1st method the task is executed in the same thread that called this task (Main thread). But if we use the 2nd method, the thread is taken from the ForkJoinPool thread pool and the main thread that called this logic is not blocked and goes on!

How to Implement Fork/Join in Code

The easiest way to explain this would be in a video, rather than giving you 50-80 lines of code and explaining it point by point.

Summary

ForkJoinPool is effective for tasks that can be easily divided into smaller subtasks. However, if the tasks are too small, using ForkJoinPool may not bring a significant performance gain.

Tudo estava claro?

Como podemos melhorá-lo?

Obrigado pelo seu feedback!

Seção 4. Capítulo 2
some-alt