Java7提供的並行執行任務框架:Fork、Join框架

1008711發表於2019-02-19

一、定義

Fork/join主要是Java7提供的一個並行執行任務的框架,Fork就是把一個大任務切分為諾乾子任務並行的執行,Join就是合併這些子任務的執行結果,最後得到大任務的結果。

如果1+2+3+……+10000 可以分割成10個子任務,每個子任務分別對1000個數進行求和,最終彙總這10個子任務的結果。

二、實現

需求:計算1+2+3+4

使用Fork、Join框架首先要考慮到時如何分割任務,如果希望每個子任務最多執行兩個數的相加,那麼我們設定分割的閾值是2,由於4個數字相加,所以這個框架會把這個任務fork成兩個子任務,子任務一負責計算1+2,子任務二負責計算3+4,然後再join兩個子任務的結果,因為是有結果的任務,所以必須繼承RecursiveTask,具體實現程式碼如下:

public class CountTask extends RecursiveTask<Integer> {

private static final int THRESHOLD = 2;
    private int start;
    private int end;

    public CountTask(int start, int end) {
this.start = start;
        this.end = end;
}

@Override
protected Integer compute() {
int sum = 0;
//如果任務足夠小就計算任務
boolean canCompute = (end - start) <= THRESHOLD;
        if (canCompute) {
for (int i = start; i <= end; i++) {
                sum += i;
}
        } else {
//如果任務大於閾值,就分裂成兩個子任務計算
int middle = (start + end) / 2;
CountTask leftTask = new CountTask(start, middle);
CountTask rightTask = new CountTask(middle + 1, end);
//執行子任務
leftTask.fork();
rightTask.fork();
//等待子任務執行完,並得到其結果
int leftResult = leftTask.join();
            int rightResult = rightTask.join();
//合併子任務
sum = leftResult + rightResult;
}
return sum;
}
}複製程式碼
public static void main(String[] strs) {

    ForkJoinPool forkJoinPool = new ForkJoinPool();
//生成一個計算任務,負責計算1+2+3+4
CountTask task = new CountTask(1, 4);
//執行一個任務
Future<Integer>  result = forkJoinPool.submit(task);
    try {
        System.out.println(result.get());
} catch (InterruptedException e) {
        e.printStackTrace();
} catch (ExecutionException e) {
        e.printStackTrace();
}

if(task.isCompletedAbnormally()){
        System.out.println(task.getException());
}

}複製程式碼

通過這個例子,我們進一步瞭解了ForkJoinTask,ForkJoinTask與一般任務的主要區別在於它需要實現compute方法,在這個方法裡,首先需要判斷任務是否足夠小,如果足夠小就直接執行任務,如果不足夠小,就必須分割成兩個子任務,每個子任務在呼叫fork方法時,又會進入compute方法,看看當前子任務是否需要繼續分割成子任務,如果不需要繼續分割,則執行當前子任務並返回結果,使用join方法會等待子任務執行完並得到其結果。

ForkJoinTask在執行任務的時候可能會丟擲異常,但是我們沒辦法在主執行緒裡直接捕獲異常,所以ForkJoinTask提供了isCompletedAbnormally()方法來檢查任務是否已經丟擲異常或已經被取消了,並且可以通過ForkJoinTask的getException方法捕獲異常

三、實現原理

ForkJoinPool由ForkJoinTask陣列和ForkJoinWorkerThread陣列組成,ForkJoinTask陣列負責將存放程式提交給ForkJoinPool的任務,而ForkJoinWorkerThread陣列負責執行這些任務。

(1).ForkJoinTask的fork方法實現原理

當我們呼叫ForkJoinTask的fork方法時,程式會呼叫ForkJoinWorkerThread的pushTask方法非同步的執行這個任務,然後立即返回結果,程式碼如下:

public final ForkJoinTask<V> fork() {
    ((ForkJoinWorkerThread) Thread.currentThread())
        .pushTask(this);
    return this;
}複製程式碼

pushTask方法把當前任務存放在ForkJoinTask陣列佇列裡。然後再呼叫ForkJoinPool的signalWork()方法喚醒或建立一個工作執行緒來執行任務,程式碼如下:

final void pushTask(ForkJoinTask<?> t) {
    ForkJoinTask<?>[] q; int s, m;
    if ((q = queue) != null) {    // ignore if queue removed
        long u = (((s = queueTop) & (m = q.length - 1)) << ASHIFT) + ABASE;
        UNSAFE.putOrderedObject(q, u, t);
        queueTop = s + 1;         // or use putOrderedInt
        if ((s -= queueBase) <= 2)
            pool.signalWork();
        else if (s == m)
            growQueue();
    }
}複製程式碼

(2).ForkJoinTask的join方法實現原理

Join方法的主要作用是阻塞當前執行緒並等待獲取結果。讓我們一起看看ForkJoinTask的join方法的實現,程式碼如下:

public final V join() {
    if (doJoin() != NORMAL)
        return reportResult();
    else
        return getRawResult();
}複製程式碼
private V reportResult() {
    int s; Throwable ex;
    if ((s = status) == CANCELLED)
        throw new CancellationException();
    if (s == EXCEPTIONAL && (ex = getThrowableException()) != null)
        UNSAFE.throwException(ex);
    return getRawResult();
}複製程式碼

首先,它呼叫了doJoin方法,通過doJoin()方法得到當前任務的狀態來判斷返回什麼結果,任務狀態有4種:已完成(NORMAL)、被取消(CANCELLED)、訊號(SIGNAL)和出現異常(EXCEPTIONAL).

①:如果任務狀態是已完成,則直接返回任務結果

②:如果任務狀態是被取消,則直接丟擲CancellationException

③:如果任務狀態是丟擲異常,則直接丟擲對應的異常

doJoin方法程式碼如下:

private int doJoin() {
    Thread t; ForkJoinWorkerThread w; int s; boolean completed;
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) {
        if ((s = status) < 0)
            return s;
        if ((w = (ForkJoinWorkerThread)t).unpushTask(this)) {
            try {
                completed = exec();
            } catch (Throwable rex) {
                return setExceptionalCompletion(rex);
            }
            if (completed)
                return setCompletion(NORMAL);
        }
        return w.joinTask(this);
    }
    else
        return externalAwaitDone();
}複製程式碼

在doJoin()方法裡,首先通過檢視任務的狀態,看任務是否已經執行完成,如果執行完成,則直接返回任務狀態,如果沒有執行完,則從任務陣列裡取出任務並執行,如果任務順利執行完成,則設定任務狀態為NORMAL,如果出現異常,則記錄異常,並將任務狀態設定為EXCEPTION.

相關文章