Fork/join框架之ForkJoinPool

概述

jdk7新增了併發框架-fork/join框架,在這種框架下,ForkJoinTask代表一個需要執行的任務,真正執行這些任務的線程是放在一個線程池(ForkJoinPool)裏面。ForkJoinPool是一個可以執行ForkJoinTask的ExcuteService,與ExcuteService不同的是它採用了work-stealing模式:所有在池中的線程嘗試去執行其他線程創建的子任務,這樣就很少有線程處於空閒狀態,非常高效。

池中維護着ForkJoinWorkerThread對象數組,數組大小由parallelism屬性決定,parallelism默認爲處理器個數

int n = parallelism << 1;
        if (n >= MAX_ID)
            n = MAX_ID;//MAX_ID=0x7fff
        else { 
            n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8;
        }
        workers = new ForkJoinWorkerThread[n + 1];
可見線程個數不會超過0x7fff。

添加線程

什麼情況下需要添加線程呢?當新的任務到來,線程池會通知其他線程前去處理,如果這時沒有處於等待的線程或者處於活動的線程非常少(這是通過ctl屬性來判斷的),就會往線程池中添加線程。

    private void addWorker() {
        Throwable ex = null;
        ForkJoinWorkerThread t = null;
        try {
            t = factory.newThread(this);
        } catch (Throwable e) {
            ex = e;
        }
        if (t == null) {  // null or exceptional factory return
            long c;       // adjust counts
            do {} while (!UNSAFE.compareAndSwapLong
                         (this, ctlOffset, c = ctl,
                          (((c - AC_UNIT) & AC_MASK) |
                           ((c - TC_UNIT) & TC_MASK) |
                           (c & ~(AC_MASK|TC_MASK)))));
            // Propagate exception if originating from an external caller
            if (!tryTerminate(false) && ex != null &&
                !(Thread.currentThread() instanceof ForkJoinWorkerThread))
                UNSAFE.throwException(ex);
        }
        else
            t.start();
    }
添加線程的代碼比較簡單,通過工廠類創建一個線程,通過調用ForkJoinWorkerThread的run方法啓動這個線程。如果失敗則恢復ctl以前的值,並終止線程。工廠類直接調用其構造方法,最終添加線程其實是在registerWorker方法完成的

for (int g;;) {
            ForkJoinWorkerThread[] ws;
            if (((g = scanGuard) & SG_UNIT) == 0 &&
                UNSAFE.compareAndSwapInt(this, scanGuardOffset,
                                         g, g | SG_UNIT)) {
                int k = nextWorkerIndex;
                try {
                    if ((ws = workers) != null) { // ignore on shutdown
                        int n = ws.length;
                        if (k < 0 || k >= n || ws[k] != null) {
                            for (k = 0; k < n && ws[k] != null; ++k)
                                ;
                            if (k == n)
                                ws = workers = Arrays.copyOf(ws, n << 1);
                        }
                        ws[k] = w;
                        nextWorkerIndex = k + 1;
                        int m = g & SMASK;
                        g = (k > m) ? ((m << 1) + 1) & SMASK : g + (SG_UNIT<<1);
                    }
                } finally {
                    scanGuard = g;
                }
                return k;
            }
            else if ((ws = workers) != null) { // help release others
                for (ForkJoinWorkerThread u : ws) {
                    if (u != null && u.queueBase != u.queueTop) {
                        if (tryReleaseWaiter())
                            break;
                    }
                }
            }

這裏有個屬性scanGuard有必要提一下,從Guard這個詞可以知道它是在保護什麼,是在保護works這個數組。當需要更新這個數組時,通過不斷地檢查scanGuard來達到保護的目的。

整個框架大量採用順序鎖,好處是不用阻塞,不好的地方是會有額外的循環。這裏也是通過循環來註冊這個線程,在循環的過程中有兩種情況發生:1、compareAndSwapInt操作成功;2、操作失敗。

第一種情況:掃描workers數組,找到一個爲空的項,並把新創建的線程放在這個位置;如果沒有找到,表示數組大小不夠,則將數組擴大2倍;

第二種情況:需要循環重新嘗試直接成功爲止,從代碼中可以看出,即使是失敗了,也不忘做一些額外的事:通知其他線程去執行沒有完成的任務。

執行任務

除了從ExecutorService繼承過來的execute和submit方法外,還對這兩個方法進行了覆蓋和重載。
public void execute(ForkJoinTask<?> task) {
    if (task == null)
        throw new NullPointerException();
    forkOrSubmit(task);
}

public void execute(Runnable task) {
    if (task == null)
        throw new NullPointerException();
    ForkJoinTask<?> job;
    if (task instanceof ForkJoinTask<?>) // avoid re-wrap
        job = (ForkJoinTask<?>) task;
    else
        job = ForkJoinTask.adapt(task, null);
    forkOrSubmit(job);
}
對參數爲Runnable的execute進行了加強,將Runnable這種普通任務適配成ForkJoinTask這種任務,然後做爲參數傳給forkOrSubmit方法統一處理。
    private <T> void forkOrSubmit(ForkJoinTask<T> task) {
        ForkJoinWorkerThread w;
        Thread t = Thread.currentThread();
        if (shutdown)
            throw new RejectedExecutionException();
        if ((t instanceof ForkJoinWorkerThread) &&
            (w = (ForkJoinWorkerThread)t).pool == this)
            w.pushTask(task);
        else
            addSubmission(task);
    }
從以上代碼可以看出,這兩種任務最終的歸屬還是不一樣,ForkJoinTask這種任務被放到線程內部的隊列裏面,普通的Runnable任務被放到線程池的隊列裏面了。
除了通過調用execute方法外,對於ForkJoinTask任務通過調用fork方法也可以向自己所在的線程隊列中添加一個任務。

終止線程池

和ExecutorService一樣,可以調用shutdown()和 shutdownNow()來終止線程,會先設置每個線程的任務狀態爲CANCELLED,然後調用Thread的interrupt方法來終止每個線程。

總結:ForkJoinPool就是一個ExcuteService,與ExcuteService不同的是:

1、ExcuteService執行Runnable/Callable任務,ForkJoinPool除了可以執行Runnable任務外,還可以執行ForkJoinTask任務;

2、ExcuteService中處於後面的任務需要等待前面任務執行後纔有機會執行,而ForkJoinPool會採用work-stealing模式幫助其他線程執行任務,即ExcuteService解決的是併發問題,而ForkJoinPool解決的是並行問題。

下一節分析Fork/Join框架中的ForkJoinTask任務

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章