📜 ⬆️ ⬇️

Fork / Join Framework in Java 7

Some time ago I was reviewing the innovations in Java 7 , and, among other things, I threatened to go more specifically for some of the innovations. It took quite a long time, in Java 7 they even managed to find a serious defect , but finally the moment came when it was time to fulfill its promise. Therefore, under the cat you can find a description and an example of using the new implementation. ExecutorService ExecutorService called ForkJoinPool ForkJoinPool . This implementation is designed specifically to simplify the parallelization of recursive tasks and solves the problem with the fact that while the sub-task is being executed, the thread that generated it cannot be used.

Brief description of the types used

ForkJoinTask

This is an abstract class that is in some sense a lightweight analogue of a stream. The bottom line is that thanks to ForkJoinPool , which will be discussed below, you can perform a significantly larger number of tasks in a small number of threads. This is achieved by the so-called work-stealing , when the sleeping task actually does not sleep, but performs other tasks. This class has many interesting methods, but we will dwell only on two: fork() , which performs an asynchronous start of the task and join() , which waits until the task is completed and returns the result of its execution. A more detailed description of all methods can be found in the official documentation .

RecursiveAction and RecursiveTask

By itself, this class is practically not used, because for the overwhelming majority of the tasks there are already ready more specific implementations: RecursiveAction RecursiveAction in case you don’t need to calculate any value, you just need to perform some action, and RecursiveTask RecursiveTask , when you still need to return something. As you can see, these two classes are similar to the existing ones. Runnable Runnable and Callable Callable .
')

ForkJoinPool

In this class, the cunning logic on the distribution of the load between the real flows is implemented. In principle, from the outside it looks almost like a conventional pool of threads, and there are no features to use.

An example of using the framework


We will invent a problem: suppose we have a tree with numbers written in the nodes. It is required to calculate the sum of all such numbers. To improve mutual understanding I will give the interface of the tree node:
 public interface Node { Collection<Node> getChildren(); long getValue(); } 

Now we will describe the recursive task, which considers the necessary sum in many threads, inheriting from RecursiveTask. We just need to override the compute method and correctly use the fork() and join methods:
 public class ValueSumCounter extends RecursiveTask<Long>{ private final Node node; public ValueSumCounter(Node node) { this.node = node; } @Override protected Long compute() { long sum = node.getValue(); List<ValueSumCounter> subTasks = new LinkedList<>(); for(Node child : node.getChildren()) { ValueSumCounter task = new ValueSumCounter(child); task.fork(); //   subTasks.add(task); } for(ValueSumCounter task : subTasks) { sum += task.join(); //       } return sum; } } 

It's simple, isn't it? It remains only to run this happiness in a separate ForkJoinPool :
 public static void main(String[] args) { Node root = getRootNode(); new ForkJoinPool().invoke(new ValueSumCounter(root)); } 

And you're done, the result of the calculation is in your pocket!

Why do I need it?


First of all, writing is so much more convenient and intuitive, than it was with the use of Future<T> . Moreover, as I wrote earlier, it is not necessary to select a dedicated real stream to perform the fork task. On the contrary, already existing threads that are currently in join-e are actively used. This, obviously, gives a significant increase in performance. I did not conduct a benchmark, because I am sure that the engineers at Oracle have already done this for me.

Good luck with your Java 7 research :)

Source: https://habr.com/ru/post/128985/


All Articles