ForkJoinPool, or ForkJoin Framework for Java Concurrent Packet Thread Pool

Posted by pytrin on Sun, 28 Jul 2019 05:41:24 +0200

Preface

This is the last thread pool implementation provided by Java concurrent packages, and also the most complex thread pool. For this part of the code is too complex, due to limited time, can only be briefly introduced. The Fork/Join framework is actually a task scheduling mechanism consisting of Fork Join Pool as thread pool, Fork Join Task (which usually implements its three abstract subclasses) as task, and Fork Join Worker Thread as specific thread entity for task execution. Generally speaking, the function of ForkJoin framework is to divide large and complex tasks recursively until they are small enough to be executed directly, so that the results of each small enough task can be returned recursively and aggregated into the results of a large task, and then the large and complex task originally submitted can be deduced by analogy. As a result, this is the same idea as recursive invocation of methods. Of course, ForkJoinPool thread pool has done a lot of complex design and implementation to improve the parallelism and throughput of tasks, the most famous of which is task stealing mechanism, which will be introduced in the next article. This article first introduces Fork Join Task and Fork Join Worker Thread.

Compared with the task performed by ThreadPool Executor described earlier, FutureTask is the implementation class of Future, and the entity of executing thread is the inner class Worker. The task performed by ForkJoinPool is the implementation class ForkJoinTask of Future, and the executing thread is ForkJoinWorker Thread.

ForkJoinWorkerThread

This class inherits Thread directly, but only to add some additional functionality without making any changes to the thread's scheduling execution. ForkJoin Worker Thread is a worker thread managed by ForkJoin Pool, which executes ForkJoin Tasks. The main purpose of this class is to maintain the task queue created by ForkJoinPool when creating an instance. Unlike the other two thread pools, the whole thread pool has only one task queue. All the worker threads managed by ForkJoinPool have their own work queue. In order to realize the task stealing mechanism, the queue is designed as a two-end one. Queues, and the first task of ForkJoin Worker Thread is to perform its own tasks in this two-end task queue. The following is its code snippet:

 1 public class ForkJoinWorkerThread extends Thread {
 2 
 3     final ForkJoinPool pool;                // This thread works ForkJoinPool pool
 4     final ForkJoinPool.WorkQueue workQueue; // The workqueue of the work stealing mechanism owned by this thread
 5 
 6     //Create in the given ForkJoinPool Executing in the pool ForkJoinWorkerThread. 
 7     protected ForkJoinWorkerThread(ForkJoinPool pool) {
 8         // Use a placeholder until a useful name can be set in registerWorker
 9         super("aForkJoinWorkerThread");
10         this.pool = pool;
11         this.workQueue = pool.registerWorker(this); //towards ForkJoinPool The execution pool registers the current worker thread. ForkJoinPool Assign a work queue to it
12     }
13     
14     //The execution of this worker thread is to execute tasks in the workqueue
15     public void run() {
16         if (workQueue.array == null) { // only run once
17             Throwable exception = null;
18             try {
19                 onStart();
20                 pool.runWorker(workQueue); //Implementing tasks in the work queue
21             } catch (Throwable ex) {
22                 exception = ex; //Recording anomalies
23             } finally {
24                 try {
25                     onTermination(exception);
26                 } catch (Throwable ex) {
27                     if (exception == null)
28                         exception = ex;
29                 } finally {
30                     pool.deregisterWorker(this, exception); //Revocation of work
31                 }
32             }
33         }
34     }
35     
36     .....
37 }

ForkJoinTask 

Like FutureTask, ForkJoinTask is also a subclass of Future, but it is an abstract class. It intersects with ForkJoinPool in the process of implementation. Therefore, it is difficult to understand all the source code without understanding ForkJoinPool. We only need to understand about it here. The function of ForkJoinTask is to split tasks according to task. The implementation (exec abstraction method) splits the tasks and waits for the execution results of the subtasks, which can be combined into the results of the parent task, and so on.

ForkJoinTask has an int-type status field, which stores the execution status of tasks such as NORMAL, CANCELLED or EXCEPTIONAL at a high 16-bit level and reserves user-defined tags at a low 16-bit level. The status is greater than or equal to 0 before the task is completed, and the values of NORMAL, CANCELLED or EXCEPTIONAL are less than 0 after the task is completed. These values are also in the order of size: 0 (initial state)> NORMAL > CANCELLED > EXCEPTIONAL.

 1 public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
 2 
 3     /** Execution status of the task */
 4     volatile int status; // accessed directly by pool and workers
 5     static final int DONE_MASK   = 0xf0000000;  // mask out non-completion bits
 6     static final int NORMAL      = 0xf0000000;  // must be negative
 7     static final int CANCELLED   = 0xc0000000;  // must be < NORMAL
 8     static final int EXCEPTIONAL = 0x80000000;  // must be < CANCELLED
 9     static final int SIGNAL      = 0x00010000;  // must be >= 1 << 16
10     static final int SMASK       = 0x0000ffff;  // short bits for tags
11 
12     // Abnormal hash table
13 
14     //An exception array thrown by a task to report to the caller. Because exceptions are rare, we do not save them directly in task In objects, you use weak reference arrays. Note that canceling exceptions does not occur in arrays, but is recorded in statue In the field
15     //Note that these are static Class attributes, all ForkJoinTask Shared.
16     private static final ExceptionNode[] exceptionTable;        //Exceptional Hash List Array
17     private static final ReentrantLock exceptionTableLock;
18     private static final ReferenceQueue<Object> exceptionTableRefQueue; //stay ForkJoinTask cover GC After recovery, the reference queue of the corresponding exception node object
19 
20     /**
21     * Fixed capacity exceptionTable.
22     */
23     private static final int EXCEPTION_MAP_CAPACITY = 32; 
24 
25     
26     //The key-value pair node of the exception array.
27     //The hash linked list array uses threads id By comparison, the array has a fixed capacity, because it only maintains tasks that are exceptionally long enough for participants to access them, so it should not become very large over time. But because we don't know the last one joiner When it's done, we have to use weak references and delete them. We do this for every operation.(So completely locked). In addition, any ForkJoinPool Some threads in the pool become isQuiescent All calls are made from time to time. helpExpungeStaleExceptions
28     static final class ExceptionNode extends WeakReference<ForkJoinTask<?>> {
29         final Throwable ex;
30         ExceptionNode next;
31         final long thrower;  // Thread that throws an exception id
32         final int hashCode;  // Store before weak references disappear hashCode
33         ExceptionNode(ForkJoinTask<?> task, Throwable ex, ExceptionNode next) {
34             super(task, exceptionTableRefQueue); //stay ForkJoinTask cover GC After recovery, the node is queued exceptionTableRefQueue
35             this.ex = ex;
36             this.next = next;
37             this.thrower = Thread.currentThread().getId();
38             this.hashCode = System.identityHashCode(task);
39         }
40     }
41 
42     .................
43 }

In addition to status recording task execution status, other fields are mainly for exception handling. ForkJoinTask uses hash array + linked list data structure (HashMap implementation method before JDK8) to store all ForkJoinTask task execution exceptions (assuming these fields are static).

fork -- Asynchronous execution of tasks

The source code is very simple, so it is not pasted. This method is actually to add tasks to the end of the current worker thread's work queue or submit queue (tasks submitted by external non-ForkJoin Worker Thread threads through submit and execute methods) through push method, waiting for execution by thread pool scheduling. This is a non-blocking instant. Return method. It's important to know that ForkJoinPool thread pool maps task queues owned by all worker threads and tasks submitted from outside to different slots in hash arrays by hashing arrays, which will be introduced in the next article. Push the new task to the end of the queue to ensure that the larger task is at the head of the queue, the smaller the task is at the end. At this time, if the thread with the task queue pop s up the task in the way of first-in-last-out (when the task queue is used in front of the stack), it will start with the smaller task first. Gradually proceed to major tasks. The other threads of the stealing task will help it complete the big task if they start stealing from the head of the column.

join -- Waiting for execution results

  1 //When the calculation is completed, the result of calculation is returned. This method and get()The difference is that exceptional completion can lead to RuntimeException or Error,Instead of ExecutionException,The calling thread is interrupted and will not be thrown InterruptedException Causes the method to return abruptly.
  2 public final V join() {
  3     int s;
  4     if ((s = doJoin() & DONE_MASK) != NORMAL)
  5         reportException(s); //Abnormal end, throw relevant exception stack information
  6     return getRawResult(); //Normal end, return result
  7 }
  8 
  9 //Wait for the task to finish executing and return to its status status,This method is implemented. join, get, quietlyJoin. Direct processing of completed, external waiting and unfork+exec In the case of the case, other cases are forwarded to ForkJoinPool.awaitJoin
 10 //If status < 0 Then return s;
 11 //Otherwise, if not ForkJoinWorkerThread ,Wait externalAwaitDone() Return
 12 //Otherwise, if (w = (wt = (ForkJoinWorkerThread)t).workQueue).tryUnpush(this) && (s = doExec()) < 0 Then return s;
 13 //Otherwise, return wt.pool.awaitJoin(w, this, 0L) 
 14 private int doJoin() {
 15     int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
 16     return (s = status) < 0 ? s :  //status A negative number indicates that the task has been completed and returned directly status. 
 17         ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
 18         (w = (wt = (ForkJoinWorkerThread)t).workQueue).
 19         tryUnpush(this) && (s = doExec()) < 0 ? s :        //call pool Execution logic and wait to return to execution result status
 20         wt.pool.awaitJoin(w, this, 0L) :        //call pool Waiting mechanism
 21         externalAwaitDone();        //No ForkJoinWorkerThread,
 22 }
 23 
 24 //Throws an exception associated with a given state(If so),Cancelled is CancellationException. 
 25 private void reportException(int s) {
 26     if (s == CANCELLED)
 27         throw new CancellationException();
 28     if (s == EXCEPTIONAL)
 29         rethrow(getThrowableException());
 30 }
 31 
 32 public abstract V getRawResult();
 33 
 34 //Returns the execution exception of a given task (if any), in order to provide accurate exception stack information, if the exception is not thrown by the current thread, a new exception with the same type of exception thrown will be attempted for the reason of the recorded exception.
 35 //If there is no such constructor, try to use an argument-free constructor and set it up initCause The method achieves the same effect, although it may contain misleading stack tracking information.
 36 private Throwable getThrowableException() {
 37     if ((status & DONE_MASK) != EXCEPTIONAL)
 38         return null;
 39         
 40     //1. Find the corresponding exception node in the Hash list array by the hash value of the current task object
 41     int h = System.identityHashCode(this); //Current Task hash value
 42     ExceptionNode e;
 43     final ReentrantLock lock = exceptionTableLock;
 44     lock.lock(); //Lock up
 45     try {
 46         expungeStaleExceptions(); //Clean up quilt GC Anomaly Nodes of Recycled Tasks
 47         ExceptionNode[] t = exceptionTable;
 48         e = t[h & (t.length - 1)]; //Getting nodes in hash array slots by modular corresponding index
 49         while (e != null && e.get() != this)
 50             e = e.next;        //Traverse to find the exception node corresponding to the current task
 51     } finally {
 52         lock.unlock();
 53     }
 54     Throwable ex;
 55     if (e == null || (ex = e.ex) == null) //Represents that no exceptions have occurred
 56         return null;
 57     if (e.thrower != Thread.currentThread().getId()) { //Exceptions but not thrown by the current thread
 58         Class<? extends Throwable> ec = ex.getClass();
 59         try {
 60             Constructor<?> noArgCtor = null;
 61             Constructor<?>[] cs = ec.getConstructors();// public ctors only
 62             //Finding the construction method by reflection and constructing new anomalies
 63             for (int i = 0; i < cs.length; ++i) {
 64                 Constructor<?> c = cs[i];
 65                 Class<?>[] ps = c.getParameterTypes();
 66                 if (ps.length == 0)
 67                     noArgCtor = c; //Record parametric-free constructions for use when the desired constructions are not found
 68                 else if (ps.length == 1 && ps[0] == Throwable.class) { 
 69                     Throwable wx = (Throwable)c.newInstance(ex); //We found what we expected. Throwable The Method of Constructing Type Parameters
 70                     return (wx == null) ? ex : wx;
 71                 }
 72             }
 73             if (noArgCtor != null) { //Instead of finding the desired constructor, new exceptions can only be created through a parametric constructor
 74                 Throwable wx = (Throwable)(noArgCtor.newInstance());
 75                 if (wx != null) {
 76                     wx.initCause(ex); //Set the original exception in
 77                     return wx;
 78                 }
 79             }
 80         } catch (Exception ignore) {
 81         }
 82     }
 83     return ex;
 84 }
 85 
 86 
 87 
 88 //Clearing Hash list arrays has been GC The exception node of the recovered task. from exceptionTableRefQueue Node refers to the queue to get the exception node and remove the corresponding node from the Hash list array
 89 private static void expungeStaleExceptions() {
 90     for (Object x; (x = exceptionTableRefQueue.poll()) != null;) {
 91         if (x instanceof ExceptionNode) {
 92             int hashCode = ((ExceptionNode)x).hashCode; //node hash
 93             ExceptionNode[] t = exceptionTable;
 94             int i = hashCode & (t.length - 1); //Moulding to get hash table index
 95             ExceptionNode e = t[i];
 96             ExceptionNode pred = null;
 97             while (e != null) {
 98                 ExceptionNode next = e.next;
 99                 if (e == x) { //Find the target node
100                     if (pred == null)
101                         t[i] = next;
102                     else
103                         pred.next = next;
104                     break;
105                 }
106                 pred = e; //Traversing the list backwards
107                 e = next;
108             }
109         }
110     }
111 }
112 
113 
114 //The main execution method of the stealing task is called unless it has been completed exec()And record the state of completion.
115 final int doExec() {
116     int s; boolean completed;
117     if ((s = status) >= 0) { //Mission not yet completed
118         try {
119             completed = exec(); call exec()And record the state of completion.
120         } catch (Throwable rex) {
121             return setExceptionalCompletion(rex); //Record exceptions and return to the relevant state, and wake up through join Threads waiting for this task.
122         }
123         if (completed)
124             s = setCompletion(NORMAL); //Update status to normal end, and wake up through join Threads waiting for this task.
125     }
126     return s;
127 }
128 
129 //The basic operation to perform this task immediately. Return true Represents that the task has been completed normally or returned false Indicates that this task is not necessarily completed(Or do you know if it's done or not?). 
130 //This method may also throw(Uncaptured)Exception to indicate an exception exit. This method is intended to support extensions and should not be invoked in other ways.
131 protected abstract boolean exec();
132 
133 //Waiting for unfinished nons ForkJoinWorkerThread Tasks submitted by threads end execution and return to task status status
134 private int externalAwaitDone() {
135 
136     //If CountedCompleter Task, Wait ForkJoinPool.common.externalHelpComplete((CountedCompleter<?>)this, 0) Return
137     //Otherwise, if ForkJoinPool.common.tryExternalUnpush(this),Return doExec() Result;
138     //Otherwise, return 0
139     int s = ((this instanceof CountedCompleter) ? // try helping
140              ForkJoinPool.common.externalHelpComplete(
141                  (CountedCompleter<?>)this, 0) :                             //Assist in completing external submissions CountedCompleter task
142              ForkJoinPool.common.tryExternalUnpush(this) ? doExec() : 0);    //Assist in completing non-external submissions CountedCompleter task
143     if (s >= 0 && (s = status) >= 0) { //It means that the task is not over yet and needs to be blocked and waited.
144         boolean interrupted = false;
145         do {
146             if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) { //Marked threads need to be waked up
147                 synchronized (this) {
148                     if (status >= 0) {
149                         try {
150                             wait(0L); //The task is not over, blocking indefinitely until awakened
151                         } catch (InterruptedException ie) {
152                             interrupted = true;
153                         }
154                     }
155                     else
156                         notifyAll(); //Has ended waking up all blocked threads
157                 }
158             }
159         } while ((s = status) >= 0);
160         if (interrupted)
161             Thread.currentThread().interrupt(); //Recovery interrupt identification
162     }
163     return s;
164 }
165 
166 
167 //Record exceptions, update status Status, wake up all waiting threads
168 private int setExceptionalCompletion(Throwable ex) {
169     int s = recordExceptionalCompletion(ex);
170     if ((s & DONE_MASK) == EXCEPTIONAL)
171         internalPropagateException(ex); //Calling hook functions to propagate exceptions
172     return s;
173 }
174 
175 /**
176  * Hook function for exception propagation support for task exception termination
177  */
178 void internalPropagateException(Throwable ex) {
179 }
180 
181 //Record exceptions and set status status
182 final int recordExceptionalCompletion(Throwable ex) {
183     int s;
184     if ((s = status) >= 0) {
185         int h = System.identityHashCode(this); //Hash value
186         final ReentrantLock lock = exceptionTableLock;
187         lock.lock();    //Lock up
188         try {
189             expungeStaleExceptions();
190             ExceptionNode[] t = exceptionTable;
191             int i = h & (t.length - 1);    
192             for (ExceptionNode e = t[i]; ; e = e.next) {
193                 if (e == null) { //No exception node was found after traversing, indicating that there is no exception node for the task in the Hash list array.
194                     t[i] = new ExceptionNode(this, ex, t[i]); //Create an exception node and insert the Hash list array with header interpolation
195                     break;
196                 }
197                 if (e.get() == this) // Hash linked list arrays have corresponding exception nodes, exit
198                     break;
199             }
200         } finally {
201             lock.unlock();
202         }
203         s = setCompletion(EXCEPTIONAL);
204     }
205     return s;
206 }
207 
208 //Mark Task Completion Logo and Wake Up Through join Threads waiting for this task.
209 private int setCompletion(int completion) {
210     for (int s;;) {
211         if ((s = status) < 0)
212             return s;
213         if (U.compareAndSwapInt(this, STATUS, s, s | completion)) { //Update status
214             if ((s >>> 16) != 0)
215                 synchronized (this) { notifyAll(); } //Wake up all waiting threads
216             return completion;
217         }
218     }
219 }

join method is the core and most complex method of ForkJoinTask. It waits for the end of task execution and returns the result of execution. If the task is cancelled, CancellationException exception is thrown. If other exceptions result in the end of exception, stack information is thrown. These exceptions may also include R caused by exhaustion of internal resources. Ejected Execution Exception, such as failure to assign internal task queues. The exception handling takes advantage of another hash array + linked list structure. This method will not throw an InterruptedException exception because the thread is interrupted, but will reset the interrupt state after the task is finished.

In the execution of this method, some abstract methods are invoked: the exec method is the entrance to the task, and the logic and split strategy of the task are all implemented by this method. Only returning true can indicate that the task is completed normally. This method can throw an exception to indicate the end of the exception. The getRawResult method is used to return the execution result of the normal end of the task. The internalPropagateException method is a callback hook function when the task is abnormal. Generally speaking, we will implement the following seemingly recursive splitting logic (pseudocode) in the exec method:

1 if task is small enough then
2. Implementing tasks;
3 Return the result;
4 else
5 is split into two subtasks t1 and t2
6 t1. fork (); // submitted to task queue
7 t2. fork (); // submitted to task queue
8 Object result = t1. join () + t2. join (); // merge result, where the plus sign only represents the merge result, not the addition operation.
9 return result; // return final result

 

We know that fork is responsible for putting tasks into queues, waiting for scheduled execution, join is waiting for execution and returning results, while join is finally called exec when executing tasks, and the tasks in exec are small enough to execute directly, otherwise the tasks will be separated through fork again. When joining the queue, its subtasks will still exec (assuming that the subtasks exec is also implemented), and then they will continue to be split, or directly executed small enough. The result of the merging of the two subtasks is the result of their father's task, and the result of the merging of the two paternal tasks is the result of their grandfather's task. This kind of recursion is to accomplish the whole task recursively.

get -- get asynchronous task results

Since ForkJoinTask is also a subclass of Future, the most important get method for obtaining asynchronous task results must also be implemented:

 1 //If necessary, wait for the calculation to complete, and then retrieve the results.
 2 public final V get() throws InterruptedException, ExecutionException {
 3     int s = (Thread.currentThread() instanceof ForkJoinWorkerThread) ? doJoin() : //yes ForkJoinWorkerThread,implement doJoin
 4             externalInterruptibleAwaitDone();    //implement externalInterruptibleAwaitDone
 5     Throwable ex;
 6     if ((s &= DONE_MASK) == CANCELLED)
 7         throw new CancellationException();    //Cancelled throw CancellationException
 8     if (s == EXCEPTIONAL && (ex = getThrowableException()) != null)
 9         throw new ExecutionException(ex);    //Throwing an exception in execution
10     return getRawResult();                    //Return to normal results
11 }
12 
13 //Obstruction ForkJoinWorkerThread Threads until completion or interruption.
14 private int externalInterruptibleAwaitDone() throws InterruptedException {
15     int s;
16     if (Thread.interrupted())
17         throw new InterruptedException();
18     if ((s = status) >= 0 &&
19         (s = ((this instanceof CountedCompleter) ?
20               ForkJoinPool.common.externalHelpComplete(
21                   (CountedCompleter<?>)this, 0) :
22               ForkJoinPool.common.tryExternalUnpush(this) ? doExec() :
23               0)) >= 0) { //Return to execution or wait for execution temporarily according to different task types
24         while ((s = status) >= 0) { //Need to block and wait
25             if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) {
26                 synchronized (this) {
27                     if (status >= 0)
28                         wait(0L);     //Blocking waiting
29                     else
30                         notifyAll(); //Wake up all waiting threads
31                 }
32             }
33         }
34     }
35     return s;
36 }

The get method is also implemented by implementing the doJoin method of the join method. The difference is that if the thread calling the get method is interrupted, the get method immediately throws an InterruptedException exception, while the join method does not. The get method uses wait/notifyAll as a thread communication mechanism to achieve blocking and wake-up. There is also a timeout version of the get method, where no code is pasted, which shows that get supports interruptible and/or timed wait for completion.

invoke -- Execute the task immediately and wait for the result to be returned

 1 //Start executing this task, and if you need to wait for it to complete and return the result, throw the corresponding exception if the underlying execution of this task occurs(Uncaptured)RuntimeException or Error. 
 2 public final V invoke() {
 3     int s;
 4     if ((s = doInvoke() & DONE_MASK) != NORMAL)
 5         reportException(s);
 6     return getRawResult();
 7 }
 8 
 9 // invoke, quietlyInvoke Realization
10 private int doInvoke() {
11     int s; Thread t; ForkJoinWorkerThread wt;
12     return (s = doExec()) < 0 ? s :      //Execute this task, complete and return to it status
13         ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ? //Execute different waiting logic according to different task types if it is not completed or needs to wait
14         (wt = (ForkJoinWorkerThread)t).pool.
15         awaitJoin(wt.workQueue, this, 0L) :    
16         externalAwaitDone();
17 }

The implementation of invoke will execute the exec method immediately using the thread calling invoke. Of course, if the implementation of the exec method uses fork/join, it will still use the recursive scheduling execution strategy of the ForkJoinPool thread pool to wait for the completion of subtasks, merge the final task results step by step, and return.

invokeAll -- Batch tasks and wait for them to finish

 1 //Perform two tasks
 2 public static void invokeAll(ForkJoinTask<?> t1, ForkJoinTask<?> t2) {
 3     int s1, s2;
 4     t2.fork(); //t2 Task Delivered to Thread Pool Scheduling Execution
 5     if ((s1 = t1.doInvoke() & DONE_MASK) != NORMAL) //t1 Tasks are executed immediately by the current thread
 6         t1.reportException(s1);         //if t1 When the exception ends, an exception is thrown, including the cancelled one. CancellationException
 7     if ((s2 = t2.doJoin() & DONE_MASK) != NORMAL) //wait for t2 end of execution
 8         t2.reportException(s2);            //if t2 When the exception ends, an exception is thrown, including the cancelled one. CancellationException
 9 }
10 
11 //Execution Task Array
12 public static void invokeAll(ForkJoinTask<?>... tasks) {
13     Throwable ex = null;
14     int last = tasks.length - 1;
15     for (int i = last; i >= 0; --i) {
16         ForkJoinTask<?> t = tasks[i];
17         if (t == null) {
18             if (ex == null) //Neither can. null
19                 ex = new NullPointerException();
20         }
21         else if (i != 0)
22             t.fork(); //Except for the first task, the thread pool is scheduled for execution.
23         else if (t.doInvoke() < NORMAL && ex == null) //Execute the first task by the current thread
24             ex = t.getException();  //Recording exceptions to the first task
25     }
26     for (int i = 1; i <= last; ++i) {
27         ForkJoinTask<?> t = tasks[i];
28         if (t != null) {
29             if (ex != null) //Abnormal completion of the first task, cancel all other tasks
30                 t.cancel(false);
31             else if (t.doJoin() < NORMAL) //Abnormal end of task, record abnormal
32                 ex = t.getException();
33         }
34     }
35     if (ex != null)
36         rethrow(ex);  //If a task ends abnormally, throw the exception of the task that ends abnormally at the front of the array
37 }
38 
39 //Execute tasks in batches and return the corresponding ones for each task ForkJoinTask Example,
40 public static <T extends ForkJoinTask<?>> Collection<T> invokeAll(Collection<T> tasks) {
41     if (!(tasks instanceof RandomAccess) || !(tasks instanceof List<?>)) {
42         invokeAll(tasks.toArray(new ForkJoinTask<?>[tasks.size()])); //Encapsulate tasks as ForkJoinTask,Call the above method implementation
43         return tasks;
44     }
45     //The following logic is related to the one above. invokeAll The same is true.
46     @SuppressWarnings("unchecked")
47     List<? extends ForkJoinTask<?>> ts = (List<? extends ForkJoinTask<?>>) tasks;
48     Throwable ex = null;
49     int last = ts.size() - 1;
50     for (int i = last; i >= 0; --i) {
51         ForkJoinTask<?> t = ts.get(i);
52         if (t == null) {
53             if (ex == null)
54                 ex = new NullPointerException();
55         }
56         else if (i != 0)
57             t.fork(); 
58         else if (t.doInvoke() < NORMAL && ex == null)
59             ex = t.getException();
60     }
61     for (int i = 1; i <= last; ++i) {
62         ForkJoinTask<?> t = ts.get(i);
63         if (t != null) {
64             if (ex != null)
65                 t.cancel(false);
66             else if (t.doJoin() < NORMAL)
67                 ex = t.getException();
68         }
69     }
70     if (ex != null)
71         rethrow(ex);
72     return tasks;
73 }

Batch tasks are implemented by the current thread (only two parameters are: the first parameter is the task in front, the smaller the index is the task in front of the array or queue). The latter task is scheduled by the thread pool and executed if multiple tasks occur. Exceptions only throw exceptions to the top task.

Quietly Invoke (), quietly Join () - - invoke and join that do not require results to be executed

Source code is not pasted, quietly Invoke (), quietly Join () these two methods just call doInvoke and doJoin, and then no more, they just do not care about the implementation of the results version of invoke and Join, of course, the exception will not be thrown when the exception ends, when performing a set of tasks and need to be the result or exception. It may be useful to delay processing until all tasks are completed.

Cancel -- Attempt to cancel the execution of a task

public boolean cancel(boolean mayInterruptIfRunning) {
    return (setCompletion(CANCELLED) & DONE_MASK) == CANCELLED;
}

The state of the unfinished task is marked as CANCELLED mainly through setCompletion, and the thread waiting for the task through join is awakened. Completed tasks cannot be cancelled. Returning true indicates successful cancellation. Note that the mayInterruptIfRunning passed in by this method is not used, so ForkJoinTask does not support interrupting tasks that have already begun to execute when the task is cancelled. Of course, subclasses of ForkJoinTask can be rewritten.

tryUnfork -- Cancel fork, which removes tasks from the task queue

1 //Cancel the task execution plan. If this task was recently passed by the current thread fork If execution is scheduled and has not yet been started in another thread, this method usually succeeds, but it is not 100%It's guaranteed to succeed.
2 public boolean tryUnfork() {
3     Thread t;
4     return (((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
5             ((ForkJoinWorkerThread)t).workQueue.tryUnpush(this) :    //In the light of ForkJoinWorkerThread Cancellation logic
6             ForkJoinPool.common.tryExternalUnpush(this));            //Cancellation logic for external submission tasks
7 }

tryUnfork tries to pop the task out of the task queue, and the thread pool naturally does not schedule the task after popping up. The implementation of this method will only succeed when the task has just been pushed into the task queue and is still at the top of the task queue. Otherwise, 100% of the tasks will fail.

Reinitialize -- reinitialize the task

1 public void reinitialize() {
2     if ((status & DONE_MASK) == EXCEPTIONAL) //Abnormal
3         clearExceptionalCompletion(); //Remove the exception node of the current task from the Hash list array, and status Reset to 0
4     else
5         status = 0;
6 }

If the task ends abnormally, the exception record of the task is cleared from the exception hash table. This method only resets the status of the task to 0, so that the task can be re-executed.

Completion status queries for tasks - isDone, isCompleted Normally, isCancelled, isCompleted Abnormally

The execution status of a task can be queried at multiple levels of detail:

  1. If the task is completed in any way (including if the task is cancelled without execution), isDone is true.
  2. If the task is completed without cancellation or exception, isCompleted Normally is true.
  3. If the task is cancelled (in this case, the getException method returns a CancellationException), isCancelled is true.
  4. If the task is cancelled or an exception is encountered, the isCompletedAbnormal exception is true, in which case getException returns the exception encountered or java.util.concurrent.CancellationException.

The adapt method for Runnable and Callable

The main purpose of adapt method is to compatible with traditional Runnable and Callable tasks. They can be encapsulated into ForkJoinTask tasks by adapt method. When ForkJoinTask is mixed with other types of tasks, these methods can be used.

Other methods

getPool can return an instance of the thread pool where the thread executing the task is located, and inForkJonPool can determine whether the current task is submitted by the ForkJoinWorkerThread thread, which generally means that the current task is a subtask after internal splitting.

The getQueuedTaskCount method returns the number of tasks that have been scheduled to be executed by the current worker thread through fork, but have not yet been executed, which is an instantaneous value. Because the task that the worker thread schedules to execute is a task submitted through fork or a task queue that enters the worker thread, the value can be known through the task.

Other methods:

 1 //Tasks may be executed when the execution pool hosting the current task is in a silent (idle) state. This approach may have passed many tasks. fork Arranged to execute, but a display join Calls are not made until they are used in the executed design.
 2 //In fact, if a batch of tasks are scheduled to execute, and do not know when they will end, if you want to arrange a task after the completion of these tasks, you can use it. helpQuiesce. 
 3 public static void helpQuiesce() {
 4     Thread t;
 5     //Different silent execution logic is invoked depending on the type of execution thread
 6     if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) {
 7         ForkJoinWorkerThread wt = (ForkJoinWorkerThread)t;
 8         wt.pool.helpQuiescePool(wt.workQueue);
 9     }
10     else
11         ForkJoinPool.quiesceCommonPool();
12 }
13 
14 //Returns the number of tasks held by the current worker thread a Number of tasks held by other worker threads that may steal their tasks b How many estimates are there? a - b The difference. If the current worker thread is not in ForkJoinPool In this case, 0 is returned.
15 //Usually this value is constant at a very small value of 3, and if it exceeds this threshold, it is processed locally.
16 public static int getSurplusQueuedTaskCount() {
17     return ForkJoinPool.getSurplusQueuedTaskCount();
18 }
19 
20 //Gets but does not remove (i.e., does not cancel the execution plan) the next task that may be scheduled for the current thread to be executed. But there is no guarantee that the task will actually be carried out immediately in the next step. This method may return even if the task exists but is inaccessible due to competition null
21 //The main purpose of this approach is to support extension, otherwise it may not be used.
22 protected static ForkJoinTask<?> peekNextLocalTask() {
23     Thread t; ForkJoinPool.WorkQueue q;
24     if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
25         q = ((ForkJoinWorkerThread)t).workQueue;
26     else
27         q = ForkJoinPool.commonSubmitterQueue();
28     return (q == null) ? null : q.peek();
29 }
30 
31 //Gets and removes (that is, cancels execution) the next task that may be scheduled for the current thread to be executed.
32 //The main purpose of this approach is to support extension, otherwise it may not be used.
33 protected static ForkJoinTask<?> pollNextLocalTask() {
34     Thread t;
35     return ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
36         ((ForkJoinWorkerThread)t).workQueue.nextLocalTask() :
37         null;
38 }
39 
40 //If the current thread is ForkJoinPool Run, retrieve, and remove (i.e., cancel execution) the next task that the current thread is about to perform. This task may have been stolen from other threads.
41 //Return nulll It doesn't necessarily mean that the task is working. ForkJoinPool In a static state. The main purpose of this approach is to support extension, otherwise it may not be used.
42 protected static ForkJoinTask<?> pollTask() {
43     Thread t; ForkJoinWorkerThread wt;
44     return ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
45         (wt = (ForkJoinWorkerThread)t).pool.nextTaskFor(wt.workQueue) :
46         null;
47 }

Some explanations

Usually ForkJoinTask is only applicable to the computation of pure functions with non-cyclic dependencies or the operation of isolated objects. Otherwise, execution may encounter some form of deadlock because tasks wait for each other in a circular manner. However, this framework supports other methods and techniques (such as using Phaser, helpQuiesce, and complete) that can be used to construct ForkJoinTask subclasses that address this dependency task. To support these uses, you can use setForkJoinTask Tag or compareAndSetForkJoinTask Tag to atomically mark a short one. The value of the type is checked using getForkJoinTaskTag. ForkJoinTask implementations do not use these protected methods or tags for any purpose, but they can be used to construct specialized subclasses that can use the provided methods to avoid re-accessing processed nodes/tasks.

Fork Join Task should perform relatively few calculations and avoid uncertain loops. Big tasks should be decomposed into smaller subtasks, usually by recursive decomposition. If the task is too large, then parallelism cannot improve throughput. If it is too small, memory and internal task maintenance overhead may exceed processing overhead.

ForkJoinTask is serializable, which enables them to be used in extensions such as remote execution frameworks. It is wise to serialize tasks only before or after execution, not during execution.

Three Abstract subclasses of ForkJoinTask

Usually, we don't implement ForkJoinTask directly, but implement its three abstract subclasses. ForkJoinTask is just to cooperate with ForkJoinPool to achieve task scheduling and execution. Usually, when we use it, we only need to provide task splitting and execution. Recursive Action is used for most calculations that do not return results, Re CursiveTask is used to calculate the return result, and CountedCompleter is used to trigger other operations after those operations have been completed.

Recursive Action -- Tasks that do not return results

 1 public abstract class RecursiveAction extends ForkJoinTask<Void> {
 2     private static final long serialVersionUID = 5232453952276485070L;
 3 
 4     /**
 5      * The main computation performed by this task.
 6      */
 7     protected abstract void compute();
 8 
 9     /**
10      * Always returns {@code null}.
11      *
12      * @return {@code null} always
13      */
14     public final Void getRawResult() { return null; }
15 
16     /**
17      * Requires null completion value.
18      */
19     protected final void setRawResult(Void mustBeNull) { }
20 
21     /**
22      * Implements execution conventions for RecursiveActions.
23      */
24     protected final boolean exec() {
25         compute();
26         return true;
27     }
28 
29 }

RecursiveAction is simple. As a task that does not return results, its getRawResult method always returns null, while the setRawResult method does nothing. It adds a compute abstraction method with no return value, which is called when ForkJoinTask is scheduled to exec method, and the exec method returns directly after executing computer. true means that the task ends normally, and the compute method is left to us to realize the logic of how to disassemble small tasks and how to perform small tasks.

RecursiveTask -- Task to return results

 1 public abstract class RecursiveTask<V> extends ForkJoinTask<V> {
 2     private static final long serialVersionUID = 5232453952276485270L;
 3 
 4     /**
 5      * The result of the computation.
 6      */
 7     V result;
 8 
 9     /**
10      * The main computation performed by this task.
11      * @return the result of the computation
12      */
13     protected abstract V compute();
14 
15     public final V getRawResult() {
16         return result;
17     }
18 
19     protected final void setRawResult(V value) {
20         result = value;
21     }
22 
23     /**
24      * Implements execution conventions for RecursiveTask.
25      */
26     protected final boolean exec() {
27         result = compute();
28         return true;
29     }
30 
31 }

RecursiveTask is also very simple. Since the result is to be returned, it defines a result field that represents the result of execution. getRawResult/setRawResult is used to manipulate the field. It adds a compute abstract method with return value to be called when ForkJoinTask is scheduled to exec method, and the exec method is executed. After compute, the return result of compute is assigned to the result as the result of task execution, and finally returned to true to indicate the normal end of the task. Similarly, the compute method is also left to us to realize the logic of how to disassemble small tasks, how to execute small tasks, and return the result of task execution.

CountedCompleter - Operation completes the operation of triggering hook function

CountedCompleter is an abstract subclass of ForkJoinTask added only in Java 8. It is more complex than the previous two. It is designed to call hook functions after the completion of the task. It can support returning results (the method getRawResult() should be rewritten to provide the results of join(), invoke() and related methods), or it can not return. Back to the result, the default getRawResult method always returns null, and the setRawResult method does nothing.

It also adds a compute abstract method with no return value, which is called when ForkJoinTask is scheduled to exec method, but its exec method always returns false after the normal execution of compute, indicating that the task is not finished properly. According to the doExec method of ForkJoinTask, it is implemented for CountedCompleter. Often ending but returning to false will result in not performing setCompletion, changing the state of the task or waking up the thread waiting for the task, which is handed over to CountedCompleter to complete itself, while computer will record the exception to the Hash list array according to the original logic if the exception ends, and then change the task. The state is EXCEPTIONAL, so the task state changes only when complete(T), ForkJoinTask.cancel(boolean), ForkJoinTask.completeExceptionally(Throwable) or compute are explicitly called.

CountedCompleter can also pass in a CountedCompleter instance when creating an instance, so it can form a tree-like task structure. All tasks on the tree can be executed in parallel, and every sub-task can be completed by tryComplete to assist its parent task. CountedCompleter has very little code. District 300 lines, but its design concept is very useful, CountedCompleters in the presence of sub-task stagnation and blocking than other forms of Fork Join Tasks more robust, but less intuitive programming. CountedCompleter is similar to other completion-based components (such as java.nio.channel.completionhandler), except that triggering onCompletion at completion may require more than one pending task to complete. Unless other initializations are made, the pending count starts at 0, but you can change the pending count (atomically) using setPending Count, addToPending Count, and compareAndSetPending Count methods. When tryComplete is invoked, if the pending operation count is non-zero, it decreases; otherwise, the onCompletion operation will be triggered, and if the CountedCompleters itself has a CountedCompleters, the CountedCompleters process will continue.

A CountedCompleter implementation class must implement a compute method, and in most cases (as shown below), tryComplete() should be called once before it returns. This class can also selectively override the method onCompletion(CountedCompleter) to perform the desired operation at normal completion, and the method onExceptional Completion (Throwable, CountedCompleter) to perform the operation at any exception.

CountedCompleters usually do not return results, in which case they are usually declared CountedCompleter < Void > and always return null as the result value. In other cases, you should override the method getRawResult to provide results from join(), invoke(), and related methods. Normally, this method should return the value of a field (or a function of one or more fields) of the CountedCompleter object, which saves the result at completion. Method setRawResult defaults to doing nothing in CountedCompleters. It is possible to override this method to maintain other objects or fields that contain the result data, but it is seldom applicable.

A CountedCompleter without other CountedCompleters (such as getCompleter returning null) can be used as a regular ForkJoinTask with additional functionality. However, any CountedCompleter with other CountedCompleter can only be used as an internal aid to other computations, so its own task status (such as those reported by ForkJoinTask.isDone, etc.) will be meaningless, and its status will only be displayed by calling complete, ForkJoinTask. cancel, ForkJoinTask. compl. The eteExceptionally (Throwable) or compute method is changed only when an exception is thrown. After any exception completes, if there is a task that has not been completed in other ways, it may propagate the exception to the CountedCompleter of the task (and its CountedCompleter, and so on). Similarly, eliminating an internal CountedCompleter only has a partial impact on the completer, so it's usually not very useful.

CountedCompleters is an abstract class. It's a little difficult to understand and its programming can achieve flexible goals. Several examples from Java doc show that CountedCompleters can recursively decompose tasks into tree-based shapes. Tree-based techniques are usually preferable to direct fork leaf tasks. Because they reduce communication between threads and improve load balancing. It can also be used in search and find process, when a thread searches the target, it can let the root node task complete, and other sub-node tasks terminate spontaneously. It can also be used to calculate the results of the parent node task merging and the grandfather task merging with its siblings, and so on. In a word, the use of CountedCompleter class is very flexible, such as running set splitting, result merging, operation scheduling and so on, which are involved in java parallel flow. Use it.

Topics: Swift Java less Programming IE