Java multithreaded Fork/Join framework (JUC)

Posted by LAEinc. Creations on Mon, 15 Jun 2020 05:08:02 +0200

Fork/Join framework

The Fork/Join framework is a framework for parallel task execution provided by Java 7. It is a framework that divides large tasks into several small tasks, and finally summarizes the results of each small task to get the results of large tasks.

characteristic:

  1. ForkJoinPool is not intended to replace ExecutorService, but is a supplement to it. In some application scenarios, the performance is better than ExecutorService.
  2. ForkJoinPool is mainly used to implement the "divide and conquer" algorithm, especially the function called recursively after divide and conquer, such as quick sort.
  3. ForkJoinPool is most suitable for computing intensive tasks. If there are I/O, inter thread synchronization, sleep() and other situations that will cause long-term thread blocking, it is best to use ManagedBlocker together.

work-stealing algorithm

The work stealing algorithm refers to a thread stealing tasks from other queues to execute. For a large task, it can be divided into several independent subtasks. In order to reduce the competition among threads, these subtasks are placed in different queues, and each of them is
The queue creates a separate thread to perform the tasks in the queue. The thread and the queue correspond one by one. When some threads complete their own tasks, there will be tasks waiting to be processed in the queue corresponding to other threads. This is that it will go to the queue of other threads to steal a task to execute. At this time, they will access the same queue, so in order to reduce the competition between the stealing task thread and the stolen task thread, the two terminal queue is usually used. The normal working thread will always take the task from the head of the two terminal queue for execution, and the stealing task thread will always take the task from the tail of the two terminal queue for execution.

Design of Fork/Join framework

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

1. ForkJoinTask: to use the Fork/Join framework, we first need to create a ForkJoinTask. This class provides a mechanism for performing fork and join in tasks. In general, we do not need to integrate the ForkJoinTask class directly, but only need to inherit its subclass. The Fork/Join framework provides two subclasses:

  • Recursive action: for tasks that do not return results
  • Recursive task: for tasks with returned results

2. ForkJoinPool: the ForkJoinTask needs to be executed through the ForkJoinPool

The subtasks separated from the task will be added to the two terminal queue maintained by the current worker thread and enter the queue head. When there is no task in the queue of one worker thread, it will randomly obtain a task (job stealing algorithm) from the end of the queue of other workers.

Several states of the ForkJoinTask:

# Status is a volatile variable, indicating the execution status of the current task. It has five statuses. A negative number indicates that the task has been completed, and a non negative number indicates that the task has not been completed.
# Among them, the completed status includes NORMAL, cancel led, and excel, while the unfinished status includes initial status 0 and SIGNAL.

volatile int status; 
# NORMAL: indicates the status of task "NORMAL" completion.
private static final int NORMAL      = -1;
# Cancel: indicates the status of task "Cancel" completion.
private static final int CANCELLED   = -2;
# Excel: indicates the status of task "abnormal" completion. Note that the above three states are all "completed" states, but the ways of completion are different.
private static final int EXCEPTIONAL = -3;
# There are other tasks that depend on the current task. Before the task ends, notify other tasks to join the results of the current task.
private static final int SIGNAL      =  1;
# 0: task initial state (in execution state), no need to wait for subtask to complete.

 

Partial source code

1.fork(); 

  • The fork method will first determine whether the current thread is an instance of the ForkJoinWorkerThread. If the conditions are met, the task task will be push ed into the two terminal queue maintained by the current thread.
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;
    }

When the current thread is not a ForkJoinWorkerThread, it will be added to the ForkJoinPool thread pool (based on the ExecutorService Implementation);
If the current thread is already a ForkJoinWorkerThread, add the task to the workQueue of the current thread;

Unlike the normal thread pool, task is not delivered to the queue in the thread pool, but to the local workQueue of the thread

task running process in ForkJoinPool:

  • a. The thread obtains tasks from the local queue in LIFO first in first out mode and executes until its own queue is empty;
  • b. Check whether other forkjoinworkerthreads have tasks that have not been executed. If so, steal them through work − sterling, FIFO first in first out to reduce competition. Give priority to the threads that once stole tasks from themselves, if any;
  • c. When the task is completed, the result is returned;

2. push();

  • In the push method, the signalWork method of ForkJoinPool is called to wake up or create a worker thread to execute the task asynchronously.
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);
                }
                else if (n >= m)
                    growArray();
            }
        }

3. join(); and related methods

The join block waits for the execution of the current ForkJoinTask to complete and returns the result. A RuntimeException or Error exception can be thrown during the execution of the task. The task execution thread can be the current thread or another worker thread.

# Judge by the task status returned by doJoin method. If it is not NORMAL, throw an exception:

   public final V join() {
        int s;
        // 1) The block waits for the task to complete. If the task completes abnormally, a RuntimeException or Error will be thrown.
        if (((s = doJoin()) & ABNORMAL) != 0) {
            reportException(s);
        }
        // 2) If the execution is successful, the original result will be returned
        return getRawResult();
    }



private void reportException(int s) {
        if (s == CANCELLED)
            throw new CancellationException();
        if (s == EXCEPTIONAL)
            rethrow(getThrowableException());
    }



# First, check the task status. If it has been completed, it will return to the task status directly. If it has not been completed, it will take the task out of the task queue and execute it.

    private int doJoin() {
        int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
        /**
         * 1)If the task has completed, return its status
         * 2)If the current thread is forkjoinworkerthread&&
         *  Then try to pull the task from the top of the resident work queue&&
         *  Perform this task in the current thread&&
         *  If the execution is successful, the task status will be returned
         * 3)If the current thread is ForkJoinWorkerThread, but the pull task fails,
         *  It means that the target task is not at the top, or another worker thread steals this task and is executing, then wait for the task to complete.
         * 4)If the current thread is not a ForkJoinWorkerThread, the block waits for the task to complete.
         */
        return (s = status) < 0 ? s :
            (t = Thread.currentThread()) instanceof ForkJoinWorkerThread ?
                    (w = (wt = (ForkJoinWorkerThread)t).workQueue).
                    tryUnpush(this) && (s = doExec()) < 0 ? s :
                        wt.pool.awaitJoin(w, this, 0L) :
                            externalAwaitDone();
    }


ForkJoinPool#WorkQueue#
        /**
         *  Remove the target task only if it is the first task at the top of the work queue and return true,
         *  Otherwise, false is returned.
         */
        boolean tryUnpush(ForkJoinTask<?> task) {
            // Read base
            final int b = base;
            // Read top
            int s = top, al; ForkJoinTask<?>[] a;
            // Task array is not empty & & there are tasks to be processed
            if ((a = array) != null && b != s && (al = a.length) > 0) {
                // Calculate read index
                final int index = al - 1 & --s;
                // Set slot to null if the top task is the current task
                if (QA.compareAndSet(a, index, task, null)) {
                    // Update the top value and return true
                    top = s;
                    VarHandle.releaseFence();
                    return true;
                }
            }
            return false;
        }


    /**
     *  Main execution methods of stealing tasks
     */
    final int doExec() {
        int s; boolean completed;
        // Task not completed
        if ((s = status) >= 0) {
            try {
                // Perform task now
                completed = exec();
            } catch (final Throwable rex) {
                completed = false;
                // Set exception status
                s = setExceptionalCompletion(rex);
            }
            // If completed normally
            if (completed) {
                // Set completion status
                s = setDone();
            }
        }
        return s;
    }


    /**
     *  Record the exception information and trigger the internalPropagateException hook function
     */
    private int setExceptionalCompletion(Throwable ex) {
        final int s = recordExceptionalCompletion(ex);
        if ((s & THROWN) != 0) {
            internalPropagateException(ex);
        }
        return s;
    }


    final int recordExceptionalCompletion(Throwable ex) {
        int s;
        // Task not completed
        if ((s = status) >= 0) {
            // Calculate the hash value of this ForkJoinTask
            final int h = System.identityHashCode(this);
            // Read lock of exception table
            final ReentrantLock lock = exceptionTableLock;
            lock.lock();
            try {
                expungeStaleExceptions();
                // Read exception table
                final ExceptionNode[] t = exceptionTable;
                // Calculation index
                final int i = h & t.length - 1;
                // Ergodic unidirectional list
                for (ExceptionNode e = t[i]; ; e = e.next) {
                    /**
                     * 1)Target slot is null
                     * 2)Has reached the end of the list
                     */
                    if (e == null) {
                        // Add this exception to the exception table
                        t[i] = new ExceptionNode(this, ex, t[i],
                                exceptionTableRefQueue);
                        break;
                    }
                    // If already joined, exit directly
                    if (e.get() == this) {
                        break;
                    }
                }
            } finally {
                lock.unlock();
            }
            // Set task status
            s = abnormalCompletion(DONE | ABNORMAL | THROWN);
        }
        return s;
    }


    /**
     *  Attempt to mark the current ForkJoinTask as completed due to cancellation or exception
     */
    private int abnormalCompletion(int completion) {
        for (int s, ns;;) {
            // If the task has been completed, return to
            if ((s = status) < 0) {
                return s;
                // Update task status
            } else if (STATUS.weakCompareAndSet(this, s, ns = s | completion)) {
                // If a thread block depends on the completion of the task, wake up all blocked threads
                if ((s & SIGNAL) != 0) {
                    synchronized (this) { notifyAll(); }
                }
                return ns;
            }
        }
    }


    /**
     *  Set the task status to DONE. If other threads are blocking waiting for the task to complete, wake up all blocked threads
     */
    private int setDone() {
        int s;
        /**
         *  1)Set task status to DONE
         *  2)If another thread is blocking waiting for the task to complete, wake up all blocked threads
         */
        if (((s = (int)STATUS.getAndBitwiseOr(this, DONE)) & SIGNAL) != 0) {
            synchronized (this) { notifyAll(); }
        }
        return s | DONE;
    }


ForkJoinPool#WorkQueue#
        /**
         *  Cycle through the target task from the top position of the work queue, remove it and execute it if found
         */
        void tryRemoveAndExec(ForkJoinTask<?> task) {
            ForkJoinTask<?>[] wa; int s, wal;
            // There are tasks to be processed in this work queue
            if (base - (s = top) < 0 && // traverse from top
                    (wa = array) != null && (wal = wa.length) > 0) {
                // Start from the top position of the work queue to scan the target task circularly, if found, remove it and execute
                for (int m = wal - 1, ns = s - 1, i = ns; ; --i) {
                    final int index = i & m;
                    final ForkJoinTask<?> t = (ForkJoinTask<?>)
                            QA.get(wa, index);
                    // 1) There are no more tasks to scan
                    if (t == null) {
                        break;
                    // 2) The current task is the target task
                    } else if (t == task) {
                        // Remove the task
                        if (QA.compareAndSet(wa, index, t, null)) {
                            top = ns;   // Move the scanned task group down one position
                            for (int j = i; j != ns; ++j) {
                                ForkJoinTask<?> f;
                                final int pindex = j + 1 & m;
                                f = (ForkJoinTask<?>)QA.get(wa, pindex);
                                QA.setVolatile(wa, pindex, null);
                                final int jindex = j & m;
                                QA.setRelease(wa, jindex, f);
                            }
                            VarHandle.releaseFence();
                            // Perform target tasks
                            t.doExec();
                        }
                        break;
                    }
                }
            }
        }

    final int awaitJoin(WorkQueue w, ForkJoinTask<?> task, long deadline) {
        int s = 0;
        /**
         *  Work queue is not null & & task is not null
         *  1)task Not a countedcomplete task
         *  2)task Is countedcomplete, attempts to steal and execute the task in the target calculation until it completes or cannot find the task
         */
        if (w != null && task != null &&
                (!(task instanceof CountedCompleter) ||
                        (s = w.localHelpCC((CountedCompleter<?>)task, 0)) >= 0)) {
            // Attempt to remove and run this task from the work queue
            w.tryRemoveAndExec(task);
            // Read the last stolen task queue ID and the current queue ID
            final int src = w.source, id = w.id;
            // Read task status
            s = task.status;
            // Task not completed
            while (s >= 0) {
                WorkQueue[] ws;
                boolean nonempty = false;
                // Get random odd index
                final int r = ThreadLocalRandom.nextSecondarySeed() | 1; // odd indices
                if ((ws = workQueues) != null) {       // scan for matching id
                    for (int n = ws.length, m = n - 1, j = -n; j < n; j += 2) {
                        WorkQueue q; int i, b, al; ForkJoinTask<?>[] a;
                        /**
                         *  The work queue to which the target index i is located is not null&&
                         *  This work queue has recently stolen tasks from the current work queue&&
                         *  This work queue has tasks to be processed&&
                         *  Help them with the task
                         */
                        if ((i = r + j & m) >= 0 && i < n &&
                                (q = ws[i]) != null && q.source == id &&
                                (b = q.base) - q.top < 0 &&
                                (a = q.array) != null && (al = a.length) > 0) {
                            // Steal the queue ID of the task
                            final int qid = q.id;
                            // Steal from base
                            final int index = al - 1 & b;
                            // Read task
                            final ForkJoinTask<?> t = (ForkJoinTask<?>)
                                    QA.getAcquire(a, index);
                            /**
                             *  Target task is not null&& 
                             *  No other queues steal this task concurrently&&
                             *  Attempt to remove this task from the target work queue
                             */
                            if (t != null && b++ == q.base && id == q.source &&
                                    QA.compareAndSet(a, index, t, null)) {
                                // If the stealing succeeds, the base value will be updated
                                q.base = b;
                                // Record the task queue ID of the most recently stolen task
                                w.source = qid;
                                // Perform target tasks
                                t.doExec();
                                // Write back the queue ID of the last stolen task
                                w.source = src;
                            }
                            nonempty = true;
                            // Steal and process a task, exit the loop
                            break;
                        }
                    }
                }
                // 1) If the target task has been completed, return to
                if ((s = task.status) < 0) {
                    break;
                // 2) No task was stolen
                } else if (!nonempty) {
                    long ms, ns; int block;
                    // 1) Non timeout mode
                    if (deadline == 0L) {
                        ms = 0L;                       // untimed
                    // 2) If it has timed out, return 
                    } else if ((ns = deadline - System.nanoTime()) <= 0L) {
                        break;                         // timeout
                    // 3) No timeout, but the remaining time < 1 ms, set it to 1 ms
                    } else if ((ms = TimeUnit.NANOSECONDS.toMillis(ns)) <= 0L)
                    {
                        ms = 1L;                       // avoid 0 for timed wait
                    }
                    // Try adding a compensation worker to handle the task
                    if ((block = tryCompensate(w)) != 0) {
                        // Blocking waiting
                        task.internalWait(ms);
                        // If the addition is successful, increase the number of active workers
                        CTL.getAndAdd(this, block > 0 ? RC_UNIT : 0L);
                    }
                    s = task.status;
                }
            }
        }
        return s;
    }

    /**
     *  Block wait if task is not completed
     */
    final void internalWait(long timeout) {
        /**
         * Write the value of (old status value | SIGNAL) to status and return the old value
         * If the task is not completed
         */
        if ((int)STATUS.getAndBitwiseOr(this, SIGNAL) >= 0) {
            synchronized (this) {
                // 1) Block wait if task is not completed
                if (status >= 0) {
                    try { wait(timeout); } catch (final InterruptedException ie) { }
                // 2) Wake up blocks all threads on this task if the task has completed   
                } else {
                    notifyAll();
                }
            }
        }
    }

    /**
     *  Block a non worker thread until the task is completed
     */
    private int externalAwaitDone() {
        int s = tryExternalHelp();
        // Task not completed & & write wake-up flag
        if (s >= 0 && (s = (int)STATUS.getAndBitwiseOr(this, SIGNAL)) >= 0) {
            boolean interrupted = false;
            synchronized (this) {
                for (;;) {
                    // 1) Task not completed
                    if ((s = status) >= 0) {
                        try {
                            // Block wait for task to complete
                            wait(0L);
                        // Worker thread interrupted, possibly thread pool terminated    
                        } catch (final InterruptedException ie) {
                            interrupted = true;
                        }
                    }
                    // 2) When the task is completed, it wakes up the waiting thread on the task
                    else {
                        notifyAll();
                        break;
                    }
                }
            }
            // Worker thread is marked with interrupt
            if (interrupted) {
                // Break this worker thread
                Thread.currentThread().interrupt();
            }
        }
        // Return to task status
        return s;
    }

    private int tryExternalHelp() {
        int s;
        /**
         * 1)If the current task has been completed, its status will be returned
         * 2)The task is not completed. If the task is countedcomplete, execute externalHelpComplete
         * 3)The task is not completed. If the task is a ForkJoinTask, try external unpush & & pull the task successfully, then execute it
         */
        return (s = status) < 0 ? s:
            this instanceof CountedCompleter ?
                    ForkJoinPool.common.externalHelpComplete(
                            (CountedCompleter<?>)this, 0) :
                                ForkJoinPool.common.tryExternalUnpush(this) ?
                                        doExec() : 0;
    }

    final boolean tryExternalUnpush(ForkJoinTask<?> task) {
        // Read thread test value
        final int r = ThreadLocalRandom.getProbe();
        WorkQueue[] ws; WorkQueue w; int n;
        // Locate to a shared queue that is not null, then try to remove this task from the target shared queue
        return (ws = workQueues) != null &&
                (n = ws.length) > 0 &&
                (w = ws[n - 1 & r & SQMASK]) != null &&
                w.trySharedUnpush(task);
    }


ForkJoinPool#WorkQueue
        boolean trySharedUnpush(ForkJoinTask<?> task) {
            boolean popped = false;
            final int s = top - 1;
            int al; ForkJoinTask<?>[] a;
            // Task array is not empty
            if ((a = array) != null && (al = a.length) > 0) {
                // Calculate target index
                final int index = al - 1 & s;
                // Read task
                final ForkJoinTask<?> t = (ForkJoinTask<?>) QA.get(a, index);
                // The read task is the target task & & try to lock the shared queue
                if (t == task &&
                        PHASE.compareAndSet(this, 0, QLOCK)) {
                    // Lock successful & & confirm no competition & & set target slot to null
                    if (top == s + 1 && array == a &&
                            QA.compareAndSet(a, index, task, null)) {
                        // Successfully ejected task
                        popped = true;
                        // Update top value
                        top = s;
                    }
                    // Release shared lock
                    PHASE.setRelease(this, 0);
                }
            }
            return popped;
        }

4.invoke(); execute the task in the current thread immediately, wait for the task to finish executing and return the result

   /**
     *  Execute the task in the current thread immediately, wait for the task to finish executing and return the result,
     *  Or throw a RuntimeException or Error exception.
     */
    public final V invoke() {
        int s;
        if (((s = doInvoke()) & ABNORMAL) != 0) {
            reportException(s);
        }
        return getRawResult();
    }

    private int doInvoke() {
        int s; Thread t; ForkJoinWorkerThread wt;
        return (s = doExec()) < 0 ? s :
            (t = Thread.currentThread()) instanceof ForkJoinWorkerThread ?
                    (wt = (ForkJoinWorkerThread)t).pool.
                    awaitJoin(wt.workQueue, this, 0L) :
                        externalAwaitDone();
    }

Use case:

RecursiveAction:

public class CustomRecursiveAction extends RecursiveAction {
    private String workload = "";
    private static final int THRESHOLD = 4;

    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 partTow = workload.substring(workload.length() / 2, workload.length());
        subtasks.add(new CustomRecursiveAction(partOne));
        subtasks.add(new CustomRecursiveAction(partTow));
        return subtasks;
    }

    private void processing(String work) {
        String result = work.toUpperCase();
        System.out.println("result - (" + result + ") - cover" + Thread.currentThread().getName() + "handle");
    }

    public static void main(String[] args) {
        CustomRecursiveAction action = new CustomRecursiveAction("ABCDEFGHIJKLMN");
        ForkJoinPool f = new ForkJoinPool();
        f.submit(action);
        f.shutdown();

    }
}

RecursiveTask:

public class CustomRecursiveActionCeshi extends RecursiveTask<List<CustomRecursiveActionCeshi>> {
    private List list;
    private static final int THRESHOLD = 20;

    public CustomRecursiveActionCeshi(List list) {
        this.list = list;
    }

    @Override
    protected List<CustomRecursiveActionCeshi> compute() {
        List<CustomRecursiveActionCeshi> subtasks = new ArrayList<>();
        if (list.size() > THRESHOLD) {
            subtasks = createSubtasks();
        } else {
            processing(list);
        }
        return subtasks;
    }

    private List<CustomRecursiveActionCeshi> createSubtasks() {
        List<CustomRecursiveActionCeshi> subtasks = new ArrayList<>();
        List one = new ArrayList();
        List two = new ArrayList<>();
        List three = new ArrayList<>();
        List four = new ArrayList<>();
        //average
        int a = list.size() / 3;
        //remainder
        int b = list.size() % 3;

        for (int i = 0; i < list.size(); i++) {
            if (i < a) {
                one.add(list.get(i));
            } else if (i < a * 2) {
                two.add(list.get(i));
            } else if (i < a * 3) {
                three.add(list.get(i));
            } else if (i < a * 3 + b) {
                four.add(list.get(i));
            }
        }
        subtasks.add(new CustomRecursiveActionCeshi(one));
        subtasks.add(new CustomRecursiveActionCeshi(two));
        subtasks.add(new CustomRecursiveActionCeshi(three));
        subtasks.add(new CustomRecursiveActionCeshi(four));
        invokeAll(subtasks);
        return subtasks;
    }

    private void processing(List list) {
        if (!list.isEmpty()) {
            System.out.println("result - (" + list + ") - cover" + Thread.currentThread().getName() + "handle");
        }
    }

    public static void main(String[] args) {
        List list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            list.add(i);
        }
        CustomRecursiveActionCeshi action = new CustomRecursiveActionCeshi(list);
        ForkJoinPool p = new ForkJoinPool();
        List<CustomRecursiveActionCeshi> invoke = p.invoke(action);
        System.out.println(invoke);
        p.shutdown();
    }
}

 

Topics: Java Excel IE