In Java Fork / Join framework uses

1 Overview

fork / join framework provided in the Java 7. It provides a number of tools, by trying to use all available processor cores to help accelerate parallel processing - this is the method and rule by division achieved .

In practice, this means that framework first "fork" , recursively independent task into smaller sub-tasks until they are simple enough to execute asynchronously.

After, "the Join" start section , wherein the results of all the sub-tasks recursively connected to a single result, or in the case of void return task, the program just waits until execution of each subtask.

In order to provide efficient parallel execution, fork / join framework uses a named ForkJoinPool thread pool that manages ForkJoinWorkerThread type of worker threads.

2. ForkJoinPool

The ForkJoinPool is the heart of the frame. It is * ExecutorService of * a realization that the management of threads and provide us with tools to obtain information about the status and performance of the thread pool.

At that time the worker thread can only perform one task, but ForkJoinPool does not create a separate thread for each sub-task. Instead, each has its own thread pool deque (or the deque ), for storing tasks.

This architecture for the work-stealing algorithm work balance thread with the help of critical load **. **

2.1 work-stealing algorithm

Simply put - idle thread busy trying to thread deques 'stolen' work.

By default, the work of the task thread gets its own double-ended queue. When it is empty, the other end of the thread from the dual busy queue tail or global thread queue entry acquisition task, since this is the maximum possible working positions located.

This approach minimizes the possibility thread competitive task. It also reduces the number of threads must be found, because it first works on the largest block of available work.

2.2 ForkJoinPool instantiated

In Java 8, visit ForkJoinPool most convenient method is to use its static instance method * commonPool (). As the name suggests, this will provide a reference to the public pools, public pools are each ForkJoinTask * default thread pool.

According to Oracle document , use the predefined public pool can reduce resource consumption, as this will prevent the creation of a separate thread pool for each task.

ForkJoinPool commonPool = ForkJoinPool.commonPool();

By creating ForkJoinPool and assign them to the utility class public static field, you can achieve the same behavior in Java 7:

public static ForkJoinPool forkJoinPool = new ForkJoinPool(2);

Now you can easily access:

ForkJoinPool forkJoinPool = PoolUtil.forkJoinPool;

Use ForkJoinPool the constructor to create a custom thread pool parallelism, and an exception handler thread factory having a specific level. In the above example, the degree of parallelism is 2. This means that the pool of pools using two processor cores.

3. ForkJoinTask

ForkJoinTask is ForkJoinPool basic types of tasks performed *. In practice, one of its two subclasses should extend: in RecursiveAction of voids tasks and RecursiveTask the return value of the task. They have an abstract method compute () *, which defines a logical task.

3.1 RecursiveAction - an example

In the following example, the unit of work to be processed is referred to by the workload of the String FIG. For demonstration purposes, the task is an absurd task: it's just simplify its input and record it.

In order to demonstrate the behavior of the branch frame, if the workload * .length () using createSubtask () * method is greater than a predetermined threshold value **, the example split task .

String is recursively divided into sub-strings created based on these substrings CustomRecursiveTask instance.

Therefore, the method returns List.

Use invokeAll () method to submit the list to the ForkJoinPool :

public class CustomRecursiveAction extends RecursiveAction {
    private String workload = "";
    private static final int THRESHOLD = 4;
    private static Logger logger =  Logger.getAnonymousLogger();
    
    public CustomRecursiveAction(String workload) {
        this.workload = workload;
    }

    @Override
    protected void compute() {
        if (workload.length() > THRESHOLD) {
            ForkJoinTask.invokeAll(createSubtasks());
        } else {
            processing(workload);
        }
    }

    private List<CustomRecursiveAction> createSubtasks() {
        List<CustomRecursiveAction> subtasks = new ArrayList<>();
        String partOne = workload.substring(0, workload.length() / 2);
        String partTwo = workload.substring(workload.length() / 2, workload.length());
        subtasks.add(new CustomRecursiveAction(partOne));
        subtasks.add(new CustomRecursiveAction(partTwo));
        return subtasks;

    }

    private void processing(String work) {
        String result = work.toUpperCase();
        logger.info("This result - (" + result + ") - was processed by " + Thread.currentThread().getName());
    }
}

This mode can be used to develop your own RecursiveAction class *. * To do this, create an object represents a total work, select the appropriate threshold, defined division of work methods, and define the method implementation.

3.2 RecursiveTask

For the return value of the task, the logic here is similar, except that the result of each sub-task in a combined result:

public class CustomRecursiveTask extends RecursiveTask<Integer> {
    private int[] arr;
    private static final int THRESHOLD = 20;
    
    public CustomRecursiveTask(int[] arr) {
        this.arr = arr;
    }

    @Override
    protected Integer compute() {
        if (arr.length > THRESHOLD) {
            return ForkJoinTask.invokeAll(createSubtasks())
                    .stream()
                    .mapToInt(ForkJoinTask::join)
                    .sum();
        } else {
            return processing(arr);
        }
    }

    private Collection<CustomRecursiveTask> createSubtasks() {
        List<CustomRecursiveTask> dividedTasks = new ArrayList<>();
        dividedTasks.add(new CustomRecursiveTask(Arrays.copyOfRange(arr, 0, arr.length / 2)));
        dividedTasks.add(new CustomRecursiveTask(Arrays.copyOfRange(arr, arr.length / 2, arr.length)));
        return dividedTasks;
    }

    private Integer processing(int[] arr) {
        return Arrays.stream(arr)
                .filter(a -> a > 10 && a < 27)
                .map(a -> a * 10)
                .sum();
    }
}

In this example, stored in the work CustomRecursiveTask class arr array representing the field. The * createSubtask () recursively to the work task into small pieces, until each piece is smaller than the threshold value . * Then, invokeAll () method will be presented to the public subtasks pull and return to the Future list.

To trigger the execution, calling * join () * method for each sub-task.

In this case, which is the use of Java * 8 Stream API completed  ;  the sum () * method is used as a final result of the combination of sub-representation of the results.

4. The mission will be submitted to the ForkJoinPool

To submit the task to the thread pool, you can use a few methods.

In *** submit () *** or *** execute () *** Method (usage thereof is the same):

forkJoinPool.execute(customRecursiveTask);

int result = customRecursiveTask.join();

The *** execute () *** Method fork tasks and wait for the results, and does not require any manual engagement:

int result = forkJoinPool.invoke(customRecursiveTask);

The ** to invokeAll () method is submitted to sequence the most convenient way ForkJoinTasks to ForkJoinPool. It task as parameters (two tasks, var args or collection), forks are returned in the order of their generation Future collection of objects.

Alternatively, you can use a separate fork () and join () method. In the fork () is a method to the task of submitting a swimming pool, but it does not trigger its execution. In join () is a method used for this purpose. In RecursiveAction the case of, the Join () only return null  ; for * RecursiveTask, * it returns the result of task execution:

customRecursiveTaskFirst.fork();

result = customRecursiveTaskLast.join();

* RecursiveTask In our example, we use invokeAll () method to submit a series of sub-tasks to the pool. Use fork () and join () * can accomplish the same task, but this will sort the results of an impact.

To avoid confusion, use invokeAll () method to ForkJoinPool submit multiple tasks usually a good idea *. *

5 Conclusion

Use fork / join framework can speed up the processing of large tasks, but to achieve this result, you should follow a few guidelines:

  • Use as few thread pool  - in most cases, the best decision is to use a thread pool for each application or system
  • ** If no specific adjustments, use the default common thread pool **
  • Use a reasonable threshold would ForkJoingTask split into subtasks
  • Avoid the  appearance ForkJoingTasks in any obstruction

image

Welcome to the number of public attention: " Java confidant " public concern number, reply " 1024 " you know, receive a free 30 classic programming books . I am concerned about the progress with 100,000 programmers. Java knowledge is updated daily Oh, look forward to your arrival!

image

Guess you like

Origin blog.csdn.net/feilang00/article/details/87775794
Recommended