Java -- JUC high concurrent programming, Fork/Join

11 Fork/Join 11.1 introduction to...
11.1 introduction to fork / join framework
11.2 Fork method
11.3 join method
11.4 exception handling of fork / join framework
11 Fork/Join

11.1 introduction to fork / join framework

Fork/Join can split a large task into multiple subtasks for parallel processing, and finally merge the subtask results into the final calculation results for output. The Fork/Join framework does two things:

  • Fork: break up a complex task and make it small
  • Join: merge the results of the split task
  1. Task segmentation: firstly, the Fork/Join framework needs to divide large tasks into sufficiently small subtasks. If the subtasks are relatively large, the subtasks should be continuously divided.
  2. Execute tasks and merge results: the divided subtasks are placed in the dual end queue, and then several startup threads obtain task execution from the dual end queue respectively. The results of the subtask execution are placed in another queue. Start a thread to get data from the queue, and then merge these data.

In the Fork/Join framework of Java, two classes are used to complete the above operations.

  • ForkJoinTask: to use the Fork/Join framework, we first need to create a ForkJoin task. This class provides a mechanism to perform fork and join in tasks. Generally, we do not need to directly integrate the ForkJoinTask class, but only need to inherit its subclasses. The Fork/Join framework provides two subclasses:

    • RecursiveAction: used for tasks that do not return results
    • RecursiveTask: used for tasks that have returned results
  • ForkJoinPool:ForkJoinTask needs to be executed through ForkJoinPool.

  • RecursiveTask: a task that can be called recursively (by itself) after inheritance.

Implementation principle of Fork/Join framework
ForkJoinPool consists of ForkJoinTask array and ForkJoinWorkerThread array. ForkJoinTask array is responsible for storing and submitting programs to ForkJoinPool, and ForkJoinWorkerThread is responsible for executing these tasks.

11.2 Fork method

Implementation principle of fork method: when we call the fork method of ForkJoinTask, the program will put the task in the workQueue of pushTask of ForkJoinWorkerThread, execute the task asynchronously, and then return the result immediately

public final ForkJoinTask<V> fork() { Thread t; if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)((ForkJoinWorkerThread)t).workQueue.push(this); else ForkJoinPool.common.externalPush(this); return this; }

The pushTask method stores the current task in the ForkJoinTask array queue. Then call the signalWork() method of ForkJoinPool to wake up or create a worker thread to execute the task. The code is as follows:

final void push(ForkJoinTask<?> task) { ForkJoinTask<?>[] a; ForkJoinPool p; int b = base, s = top, n; if ((a = array) != null) { // ignore if queue removed int m = a.length - 1; // fenced write for task visibility U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);U.putOrderedInt(this, QTOP, s + 1); if ((n = s - b) <= 1) { if ((p = pool) != null) p.signalWork(p.workQueues, this);//implement } else if (n >= m) growArray(); } }

11.3 join method

The main function of the join method is to block the current thread and wait for the result. Let's take a look at the implementation of the join method of ForkJoinTask. The code is as follows:

public final V join() { int s; if ((s = doJoin() & DONE_MASK) != NORMAL) reportException(s); return getRawResult(); }

It first calls the doJoin method to get the status of the current task through the doJoin() method to judge what results are returned. There are four task states:

NORMAL, CANCELLED, SIGNAL and exception

  • If the task status is completed, the task result will be returned directly.
  • If the task status is cancelled, cancelationexception will be thrown directly
  • If the task status is to throw an exception, the corresponding exception will be thrown directly

The process of doJoin() method is as follows:

  1. First, check the status of the task to see whether the task has been executed. If the execution is completed, you will directly return to the task status;
  2. If the execution is not completed, the task is taken from the task array and executed.
  3. If the task is successfully executed, set the task status to NORMAL. If an exception occurs, record the exception and set the task status to EXCEPTIONAL.

11.4 exception handling of fork / join framework

ForkJoinTask may throw exceptions during execution, but we can't catch exceptions directly in the main thread, so ForkJoinTask provides isCompletedAbnormally() method to check whether the task has thrown exceptions or been cancelled, and you can use the
getException method gets the exception.

The getException method returns the Throwable object. If the task is cancelled, it returns the Throwable object.
CancellationException. If the task is not completed or no exception is thrown, null is returned.

11.5 introduction cases

Scenario: generate a calculation task, calculate 1 + 2 + 3... + 1000, and divide a subtask every 100 numbers

import java.util.concurrent.RecursiveTask; /** * Recursive accumulation */ public class TaskExample extends RecursiveTask<Long> { private int start; private int end; private long sum; public TaskExample(int start, int end){ this.start = start; this.end = end; } protected Long compute() { System.out.println("task" + start + "=========" + end + "Accumulation start"); //More than 100 numbers are added and divided, less than direct addition if(end - start <= 100){ for (int i = start; i <= end; i++) { //accumulation sum += i; } }else { //Cut into 2 pieces int middle = start + 100; //Recursive call, split into 2 small tasks TaskExample taskExample1 = new TaskExample(start, middle); TaskExample taskExample2 = new TaskExample(middle + 1, end); //Execution: asynchronous taskExample1.fork(); taskExample2.fork(); //Get execution results from synchronization blocking sum = taskExample1.join() + taskExample2.join(); } //Return after adding return sum; } } import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinTask; /** * Branch merger case */public class ForkJoinPoolDemo { /** * Generate a calculation task to calculate 1 + 2 + 3.......... + 1000 * @param args */ public static void main(String[] args) { //Define task TaskExample taskExample = new TaskExample(1, 1000); //Define execution object ForkJoinPool forkJoinPool = new ForkJoinPool(); //Join task execution ForkJoinTask<Long> result = forkJoinPool.submit(taskExample); //Output results try { System.out.println(result.get()); }catch (Exception e){ e.printStackTrace(); }finally { forkJoinPool.shutdown(); } } }

30 October 2021, 23:16 | Views: 1176

Add new comment

For adding a comment, please log in
or create account

0 comments