Fork/Join框架之Fork、Join操作

Fork

Fork就是一個不斷分枝的過程,在當前任務的基礎上長出n多個子任務。
當一個ForkJoinTask任務調用fork()方法時,當前線程會把這個任務放入到queue數組的queueTop位置,然後執行以下兩句代碼:
if ((s -= queueBase) <= 2)
    pool.signalWork();
else if (s == m)
    growQueue();
其中s=queueTop,m爲數組length減1。else if部分,表示數組所有元素都滿了,需要擴容,不難理解。if部分表示當數組元素比較少時(1或者2),就調用signalWork()方法。signalWork()方法做了兩件事:1、喚配當前線程;2、當沒有活動線程時或者線程數較少時,添加新的線程。

Join

Join是一個不斷等待,獲取任務執行結果的過程。
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();
}
(1)第4行,(s=status)<0表示這個任務被執行完,直接返回執行結果狀態,上層捕獲到狀態後,決定是要獲取結果還是進行錯誤處理;
(2)第6行,從queue中取出這個任務來執行,如果執行完了,就設置狀態爲NORMAL;
(3)前面unpushTask()方法在隊列中沒有這個任務時會返回false,15行調用joinTask等待這個任務完成。
(4)由於ForkJoinPool中有一個數組叫submissionQueue,通過submit方法調用而且非ForkJoinTask這種任務會被放到這個隊列中。這種任務有可能被非ForkJoinWorkerThread線程執行,第18行表示如果是這種任務,等待它執行完成。
下面來看joinTask方法
final int joinTask(ForkJoinTask<?> joinMe) {
    ForkJoinTask<?> prevJoin = currentJoin;
    currentJoin = joinMe;
    for (int s, retries = MAX_HELP;;) {
        if ((s = joinMe.status) < 0) {
            currentJoin = prevJoin;
            return s;
        }
        if (retries > 0) {
            if (queueTop != queueBase) {
                if (!localHelpJoinTask(joinMe))
                    retries = 0;           // cannot help
            }
            else if (retries == MAX_HELP >>> 1) {
                --retries;                 // check uncommon case
                if (tryDeqAndExec(joinMe) >= 0)
                    Thread.yield();        // for politeness
            }
            else
                retries = helpJoinTask(joinMe) ? MAX_HELP : retries - 1;
        }
        else {
            retries = MAX_HELP;           // restart if not done
            pool.tryAwaitJoin(joinMe);
        }
    }
}
(1)這裏有個常量MAX_HELP=16,表示幫助join的次數。第11行,queueTop!=queueBase表示本地隊列中有任務,如果這個任務剛好在隊首,則嘗試自己執行;否則返回false。這時retries被設置爲0,表示不能幫助,因爲自已隊列不爲空,自己並不空閒。在下一次循環就會進入第24行,等待這個任務執行完成。
(2)第20行helpJoinTask()方法返回false時,retries-1,連續8次都沒有幫到忙,就會進入第14行,調用yield讓權等待。沒辦法人口太差,想做點好事都不行,只有停下來休息一下。
(3)當執行到第20行,表示自己隊列爲空,可以去幫助這個任務了,下面來看是怎麼幫助的?
outer:for (ForkJoinWorkerThread thread = this;;) {
    // Try to find v, the stealer of task, by first using hint
    ForkJoinWorkerThread v = ws[thread.stealHint & m];
    if (v == null || v.currentSteal != task) {
        for (int j = 0; ;) {        // search array
            if ((v = ws[j]) != null && v.currentSteal == task) {
                thread.stealHint = j;
                break;              // save hint for next time
            }
            if (++j > m)
                break outer;        // can't find stealer
        }
    }
    // Try to help v, using specialized form of deqTask
    for (;;) {
        ForkJoinTask<?>[] q; int b, i;
        if (joinMe.status < 0)
            break outer;
        if ((b = v.queueBase) == v.queueTop ||
            (q = v.queue) == null ||
            (i = (q.length-1) & b) < 0)
            break;                  // empty
        long u = (i << ASHIFT) + ABASE;
        ForkJoinTask<?> t = q[i];
        if (task.status < 0)
            break outer;            // stale
        if (t != null && v.queueBase == b &&
            UNSAFE.compareAndSwapObject(q, u, t, null)) {
            v.queueBase = b + 1;
            v.stealHint = poolIndex;
            ForkJoinTask<?> ps = currentSteal;
            currentSteal = t;
            t.doExec();
            currentSteal = ps;
            helped = true;
        }
    }
    // Try to descend to find v's stealer
    ForkJoinTask<?> next = v.currentJoin;
    if (--levels > 0 && task.status >= 0 &&
        next != null && next != task) {
        task = next;
        thread = v;
    }
}
(1)通過查看stealHint這個字段的註釋可以知道,它表示最近一次誰來偷過我的queue中的任務。因此通過stealHint並不能找到當前任務被誰偷了?所以第4行v.currentSteal != task完全可能。這時還有一個辦法找到這個任務被誰偷了,看看currentSteal這個字段的註釋表示最近偷的哪個任務。這裏掃描所有偷來的任務與當前任務比較,如果相等,就是這個線程偷的。如果這兩種方法都不能找到小偷,只能等待了。
(2)當找到了小偷後,以其人之身還之其人之道,從小偷那裏偷任務過來,相當於你和小偷共同執行你的任務,會加速你的任務完成。
(3)小偷也是爺,如果小偷也在等待一個任務完成,權利反轉(小偷等待的這個任務做爲當前任務,小偷扮演當事人角色把前面的流程走一遍),這是一個遞歸的過程。


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