(十二)徹悟并發(fā)之JUC分治思想產(chǎn)物-ForkJoin分支合并框架原理剖析下篇

引言

在《(十二)徹悟并發(fā)之JUC分治思想產(chǎn)物-ForkJoin分支合并框架原理剖析上篇》中,我們?cè)醪搅私饬薋orkJoin分支合并框架的使用,也分析框架的成員構(gòu)成以及任務(wù)提交和創(chuàng)建工作的原理實(shí)現(xiàn),在本篇?jiǎng)t會(huì)對(duì)框架的任務(wù)執(zhí)行、任務(wù)掃描、線程掛起、結(jié)果合并以及任務(wù)竊取的源碼實(shí)現(xiàn)進(jìn)行分析。

一、工作線程執(zhí)行任務(wù)/工作竊取實(shí)現(xiàn)過(guò)程

在上篇的最后,從signalWork() -> tryAddWorker() -> createWorker() -> newThread() -> ForkJoinWorkerThread() -> registerWorker() -> deregisterWorker()這條路線分析完了工作線程的注冊(cè)與銷毀原理實(shí)現(xiàn)。下面接著繼續(xù)來(lái)分析工作線程執(zhí)行任務(wù)的過(guò)程,先回到之前的createWorker()方法:

// ForkJoinPool類 → createWorker()方法
private boolean createWorker() {
    ForkJoinWorkerThreadFactory fac = factory;
    Throwable ex = null;
    ForkJoinWorkerThread wt = null;
    try {
        if (fac != null && (wt = fac.newThread(this)) != null) {
            // 創(chuàng)建成功則調(diào)用start()方法執(zhí)行
            wt.start();
            return true;
        }
    } catch (Throwable rex) {
        ex = rex;
    }
    // 如果創(chuàng)建過(guò)程出現(xiàn)異常則注銷線程
    deregisterWorker(wt, ex);
    return false;
}

可以很明顯的看到,創(chuàng)建線程成功后則會(huì)開始調(diào)用start()方法執(zhí)行任務(wù),最終會(huì)找到run()方法執(zhí)行它:

// ForkJoinWorkerThread類 → run()方法
public void run() {
    // 如果任務(wù)數(shù)組不為空
    if (workQueue.array == null) {
        Throwable exception = null;
        try {
            // 鉤子函數(shù),用于拓展,這里是空實(shí)現(xiàn)
            onStart();
            // 使用線程池的runWorker方法執(zhí)行隊(duì)列任務(wù)
            pool.runWorker(workQueue);
        } catch (Throwable ex) {
            // 如果執(zhí)行過(guò)程中出現(xiàn)異常則先記錄
            exception = ex;
        } finally {
            try {
                // 鉤子函數(shù),報(bào)告異常
                onTermination(exception);
            } catch (Throwable ex) {
                if (exception == null)
                    exception = ex;
            } finally {
                // 如果執(zhí)行出現(xiàn)異常,注銷線程
                pool.deregisterWorker(this, exception);
            }
        }
    }
}

// ForkJoinPool類 → runWorker()方法
final void runWorker(WorkQueue w) {
    // 初始化任務(wù)數(shù)組,任務(wù)數(shù)組一開始是沒(méi)有初始化的
    // 這個(gè)方法是初始化或兩倍擴(kuò)容數(shù)組
    w.growArray();  
    // 獲取注冊(cè)隊(duì)列時(shí)記錄的用于計(jì)算索引的隨機(jī)種子
    int seed = w.hint;
    // 如果種子為0,那么則改為1,避免使用0
    int r = (seed == 0) ? 1 : seed; 
    // 死循環(huán)
    for (ForkJoinTask<?> t;;) {
        // 掃描任務(wù):在池的隊(duì)列數(shù)組中隨機(jī)選擇工作隊(duì)列,獲取任務(wù)執(zhí)行
        if ((t = scan(w, r)) != null)
            // 如果獲取到任務(wù)則執(zhí)行
            w.runTask(t);
        // 沒(méi)有掃描到任務(wù)則嘗試自旋或掛起阻塞
        else if (!awaitWork(w, r))
            break;
        // 每次執(zhí)行完后修改隨機(jī)值,換個(gè)隊(duì)列獲取任務(wù)
        r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
    }
}

// ForkJoinPool類 → scan()方法
private ForkJoinTask<?> scan(WorkQueue w, int r) {
    WorkQueue[] ws; int m;
    // 如果隊(duì)列數(shù)組不為空并且任務(wù)隊(duì)列已經(jīng)初始化且不為空
    if ((ws = workQueues) != null && (m = ws.length - 1) > 0 && w != null) {
        // 獲取當(dāng)前隊(duì)列scanState,最開始為隊(duì)列在數(shù)組中的下標(biāo)
        int ss = w.scanState; 
        // r&m:隨機(jī)得到一個(gè)數(shù)組中的下標(biāo),oldSum/checkSum:比較效驗(yàn)和的標(biāo)識(shí)
        // 開啟循環(huán)
        for (int origin = r & m, k = origin, oldSum = 0, checkSum = 0;;) {
            WorkQueue q; ForkJoinTask<?>[] a; ForkJoinTask<?> t;
            int b, n; long c;
            // 如果隨機(jī)出的下標(biāo)位置隊(duì)列不為空
            if ((q = ws[k]) != null) {
                // 判斷隊(duì)列中有沒(méi)有任務(wù)
                if ((n = (b = q.base) - q.top) < 0 &&
                    (a = q.array) != null) {
                    // FIFO模式,通過(guò)內(nèi)存偏移量計(jì)算出棧底/隊(duì)頭位置
                    long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
                    // 獲取棧底的任務(wù)
                    if ((t = ((ForkJoinTask<?>)
                              U.getObjectVolatile(a, i))) != null &&
                        q.base == b) {
                        // 如果工作線程處于活躍狀態(tài)
                        if (ss >= 0) {
                            // 嘗試?yán)肅AS機(jī)制搶占線程(可能存在多個(gè)線程)
                            if (U.compareAndSwapObject(a, i, t, null)) {
                                // 搶占任務(wù)成功后將棧底挪一個(gè)位置
                                // 方便其他線程繼續(xù)獲取任務(wù)
                                q.base = b + 1;
                                // 如果隊(duì)列中還剩有其他任務(wù)
                                if (n < -1)       // signal others
                                    // 新建或喚醒一條線程繼續(xù)處理
                                    signalWork(ws, q);
                                return t;
                            }
                        }
                        // 如果當(dāng)前線程未激活,處于阻塞狀態(tài)
                        else if (oldSum == 0 &&   // try to activate
                                 w.scanState < 0)
                             // 喚醒線程
                            tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
                    }
                    // 更新一次scanState值(因?yàn)榍懊婵赡軉拘蚜司€程)
                    if (ss < 0)                   // refresh
                        ss = w.scanState;
                    // 獲取一個(gè)新的隨機(jī)值,用于隨機(jī)下一個(gè)索引位置
                    r ^= r << 1; r ^= r >>> 3; r ^= r << 10;
                    // 根據(jù)新的隨機(jī)種子計(jì)算出一個(gè)新的下標(biāo)索引
                    origin = k = r & m;           // move and rescan
                    // 效驗(yàn)和的標(biāo)識(shí)復(fù)位
                    oldSum = checkSum = 0;
                    // 結(jié)束本次循環(huán),繼續(xù)下次循環(huán)
                    continue;
                }
                // 如果沒(méi)有獲取到任務(wù),checkSum+1,表示遍歷完了一個(gè)位置
                // 用于效驗(yàn)
                checkSum += b;
            }
            // k=(k+1)&m代表去隊(duì)列數(shù)組的下個(gè)位置繼續(xù)查找下個(gè)坑位的隊(duì)列,
            // 如果 ==origin 了,代表已經(jīng)遍歷了所有的隊(duì)列
            if ((k = (k + 1) & m) == origin) {    // continue until stable
                // 如果工作線程還處于活躍狀態(tài)并且掃描完成整個(gè)隊(duì)列后,
                // 效驗(yàn)和 還未發(fā)生改變,那代表著沒(méi)有新的任務(wù)提交到線程池
                if ((ss >= 0 || (ss == (ss = w.scanState))) &&
                    oldSum == (oldSum = checkSum)) {
                    // 如果活躍狀態(tài)變?yōu)榱?lt;0,代表已經(jīng)處于不活躍狀態(tài)
                    // 那么則退出掃描,返回null,回到runWorker()阻塞線程
                    if (ss < 0 || w.qlock < 0)    // already inactive
                        break;
                    // 滅活操作(滅活后的線程被稱為失活狀態(tài)):
                    //      先將當(dāng)前scanState變?yōu)樨?fù)數(shù)
                    int ns = ss | INACTIVE;       // try to inactivate
                    // 在ctl中減去一個(gè)活躍線程數(shù),
                    // 并且將失活的ss保存到ctl的低三十二位
                    long nc = ((SP_MASK & ns) |
                               (UC_MASK & ((c = ctl) - AC_UNIT)));
                    // 用工作線程的stackPred成員保存上一個(gè)失活線程的
                    // scanState,從而形成一個(gè)阻塞棧,ctl的低32位保存棧頂
                    w.stackPred = (int)c;         // hold prev stack top
                    // 更新當(dāng)前工作線程的scanState
                    U.putInt(w, QSCANSTATE, ns);
                    // 使用cas機(jī)制更新ctl值
                    if (U.compareAndSwapLong(this, CTL, c, nc))
                        ss = ns;
                    else
                        // 如果更新失敗則退出回滾,繼續(xù)掃描任務(wù),因?yàn)閏as過(guò)程
                        // 中,導(dǎo)致失敗的原因就一個(gè):ctl值發(fā)生了
                        // 改變,這可能是有新任務(wù)提交進(jìn)來(lái)了之后,喚醒或
                        // 添加了一條線程
                        w.scanState = ss;         // back out
                }
                // 檢查標(biāo)識(shí)復(fù)位
                checkSum = 0;
            }
        }
    }
    // 如果未掃描到任務(wù)則直接返回null,并在外邊的runWorker()發(fā)生阻塞
    return null;
}

ok,如上是整個(gè)線程工作的源碼實(shí)現(xiàn),重點(diǎn)在于任務(wù)掃描的實(shí)現(xiàn)過(guò)程,同時(shí)它也是理解比較困難的一個(gè)地方,下面來(lái)整體梳理一下整個(gè)線程工作以及掃描任務(wù)的流程:

  • ①線程start()啟動(dòng)之后會(huì)找到run()方法,然后會(huì)開始去競(jìng)爭(zhēng)線程池中的共享任務(wù)
  • ②初始化線程的工作隊(duì)列,同時(shí)獲取注冊(cè)隊(duì)列時(shí)計(jì)算索引的隨機(jī)種子
  • ③開啟循環(huán)掃描,通過(guò)隨機(jī)種子計(jì)算出池中隊(duì)列數(shù)組中的一個(gè)下標(biāo)索引
  • ④判斷隨機(jī)出來(lái)的索引位置是否為空:
    • 不為空:判斷隊(duì)列中是否存在任務(wù):
      • 存在:判斷當(dāng)前線程狀態(tài)是否為活躍狀態(tài):
        • 是:通過(guò)cas機(jī)制,以FIFO的模式取出棧底/隊(duì)頭的任務(wù),如果還剩余任務(wù)則新建或喚醒一條新的線程繼續(xù)處理,然后返回獲取到的任務(wù)
        • 否:先將ctl中記錄的失活線程喚醒,隨機(jī)計(jì)算一個(gè)新的位置,跳出本次循環(huán),繼續(xù)下次循環(huán)
      • 不存在:更新scanState并隨機(jī)計(jì)算一個(gè)新的位置,跳出本次循環(huán),繼續(xù)下次循環(huán)
      • 如果存在任務(wù)但是沒(méi)有獲取到任務(wù),代表沒(méi)有有其他線程搶了任務(wù),checkSum+1,隨機(jī)計(jì)算一個(gè)新的位置,跳出本次循環(huán),繼續(xù)下次循環(huán)
    • 為空:代表該位置不存在隊(duì)列,找到下一個(gè)位置,依次類推....
  • ④如果隊(duì)列為空時(shí)會(huì)找到下一個(gè)位置,然后接著重復(fù)第④步
  • ⑤如果遍歷完所有隊(duì)列還是沒(méi)有獲取到任務(wù),并且掃描期間也沒(méi)有新的任務(wù)提交到線程池時(shí),先判斷工作線程的活躍狀態(tài):
    • 失活(不活躍)狀態(tài):直接退出循環(huán)回到runWorker()方法自旋或掛起阻塞
    • 活躍狀態(tài):進(jìn)行滅活操作,利用cas機(jī)制減去ctl中一個(gè)活躍線程數(shù),同時(shí)將當(dāng)前線程的scanState值記錄到ctl的低32位做為棧頂,使用stackPred保存上一條失活線程的scanState值,從而形成一個(gè)阻塞棧
    • 如果滅活操作失敗,則代表ctl發(fā)生了改變,代表有新任務(wù)提交進(jìn)了線程池,則取消滅活操作
    • 線程如果是處于活躍狀態(tài),在掃描一圈沒(méi)有獲取到任務(wù)之后,會(huì)再重新掃描一次所有隊(duì)列,在第二遍掃描中線程是有機(jī)會(huì)被重新“復(fù)活(喚醒)”的
  • ⑥當(dāng)線程第二圈掃描后,依舊未獲取到任務(wù),那么當(dāng)前線程會(huì)退出循環(huán),返回空
  • ⑦掃描完畢后回到runWorker()方法,在該方法中會(huì)判斷掃描結(jié)果是否為空:
    • 非空:調(diào)用runTask()執(zhí)行掃描獲取到的任務(wù)
    • 為空:調(diào)用awaitWork()自旋或掛起阻塞線程,直至有新任務(wù)提交后喚醒
  • ⑧如果獲取任務(wù)成功,在執(zhí)行過(guò)程中出現(xiàn)異常則報(bào)告異常信息并注銷線程

線程工作以及掃描的整個(gè)流程會(huì)比較長(zhǎng),尤其是有些小伙伴在理解scan()方法的多次掃描有些困難,線程在第一圈掃描時(shí)未獲取到任務(wù),會(huì)先滅活然后再掃描一圈,如果第二圈掃描到了任務(wù)則會(huì)“復(fù)活”滅活線程,然后再掃描一圈。如果第二圈掃描同樣未掃描到任務(wù),那么則直接退出循環(huán)。下面來(lái)個(gè)流程圖加深理解:

ForkJoin框架執(zhí)行/掃描/竊取過(guò)程

在掃描的實(shí)現(xiàn)中,其實(shí)也是包含了任務(wù)竊取的實(shí)現(xiàn)的,因?yàn)樵趻呙璧倪^(guò)程中是不會(huì)區(qū)分偶數(shù)隊(duì)列和奇數(shù)隊(duì)列,而且將所有隊(duì)列都進(jìn)行掃描,只要有任務(wù)就獲取執(zhí)行,而獲取任務(wù)的方式是通過(guò)FIFO方式進(jìn)行的,代表著共享隊(duì)列中的任務(wù)獲取以及工作竊取是通過(guò)獲取隊(duì)列頭部/棧底的元素實(shí)現(xiàn)。而線程在執(zhí)行自己工作隊(duì)列中的任務(wù)時(shí),是通過(guò)LIFO的模式進(jìn)行的,是從隊(duì)列尾部/棧頂獲取任務(wù)執(zhí)行,這樣做的好處是可以避免工作竊取和本地執(zhí)行時(shí)的CAS競(jìng)爭(zhēng)。
Fork/Join框架竊取原理

ok,接著來(lái)看看任務(wù)執(zhí)行以及線程掛起的實(shí)現(xiàn):

// FrokJoinPool類 → runTask()方法
final void runTask(ForkJoinTask<?> task) {
    // 如果任務(wù)不為空
    if (task != null) {
        // scanState&=~SCANNING會(huì)把scanState變成偶數(shù),表示正在執(zhí)行任務(wù)
        scanState &= ~SCANNING; // mark as busy
        // 執(zhí)行任務(wù)
        (currentSteal = task).doExec();
        // 執(zhí)行完任務(wù)后將維護(hù)偷取到的任務(wù)的成員置空
        U.putOrderedObject(this, QCURRENTSTEAL, null);
        // 執(zhí)行本地任務(wù):工作線程自身隊(duì)列中的任務(wù)
        execLocalTasks();
        ForkJoinWorkerThread thread = owner;
        // 竊取任務(wù)計(jì)數(shù)
        if (++nsteals < 0)
            // 疊加到ForkJoinPool的stealCounter成員中
            transferStealCount(pool);
        // 執(zhí)行完成后,將狀態(tài)從執(zhí)行重新改為掃描狀態(tài)
        scanState |= SCANNING;
        // 執(zhí)行鉤子函數(shù)
        if (thread != null)
            thread.afterTopLevelExec();
    }
}

// FrokJoinPool類 → execLocalTasks()方法
final void execLocalTasks() {
    int b = base, m, s;
    ForkJoinTask<?>[] a = array;
    // 如果自身工作隊(duì)列中有任務(wù)
    if (b - (s = top - 1) <= 0 && a != null &&
        (m = a.length - 1) >= 0) {
        // 如果自身隊(duì)列被指定成了FIFO模式執(zhí)行
        if ((config & FIFO_QUEUE) == 0) {
            for (ForkJoinTask<?> t;;) {
                // 從棧頂/隊(duì)列頭部獲取任務(wù)執(zhí)行
                if ((t = (ForkJoinTask<?>)U.getAndSetObject
                     (a, ((m & s) << ASHIFT) + ABASE, null)) == null)
                    break;
                U.putOrderedInt(this, QTOP, s);
                //執(zhí)行任務(wù)
                t.doExec();
                if (base - (s = top - 1) > 0)
                    break;
            }
        }
        else
            // 如果沒(méi)有則直接以LIFO模式從棧底獲取任務(wù)執(zhí)行
            pollAndExecAll();
    }
}
// FrokJoinPool類 → pollAndExecAll()方法
final void pollAndExecAll() {
    // 棧底/隊(duì)列尾部獲取任務(wù)
    for (ForkJoinTask<?> t; (t = poll()) != null;)
        t.doExec();
}
// WorkerQueue類 → poll()方法
final ForkJoinTask<?> poll() {
    ForkJoinTask<?>[] a; int b; ForkJoinTask<?> t;
    // 任務(wù)隊(duì)列不為空
    while ((b = base) - top < 0 && (a = array) != null) {
        // 從棧底/隊(duì)列尾部取值
        int j = (((a.length - 1) & b) << ASHIFT) + ABASE;
        t = (ForkJoinTask<?>)U.getObjectVolatile(a, j);
        // 檢查是否被其他線程搶占
        if (base == b) {
            if (t != null) {
                // 置空
                if (U.compareAndSwapObject(a, j, t, null)) {
                    base = b + 1;
                    return t;
                }
            }
            // 如果隊(duì)列中沒(méi)有了任務(wù)則退出
            else if (b + 1 == top) // now empty
                break;
        }
    }
    return null;
}

// FrokJoinPool類 → awaitWork()方法
private boolean awaitWork(WorkQueue w, int r) {
    // 如果隊(duì)列已經(jīng)被注銷,直接返回
    if (w == null || w.qlock < 0)
        return false;
    // 開啟循環(huán)(w.stackPred:上個(gè)阻塞線程的scanState值)
    for (int pred = w.stackPred, spins = SPINS, ss;;) {
        // 如果當(dāng)前線程被“復(fù)活/喚醒”則直接退出
        if ((ss = w.scanState) >= 0)
            break;
        
        // 自旋操作:在掛起線程前會(huì)隨機(jī)自旋一段時(shí)間
        else if (spins > 0) {
            // 通過(guò)隨機(jī)種子以及自旋數(shù)實(shí)現(xiàn)隨機(jī)自旋
            r ^= r << 6; r ^= r >>> 21; r ^= r << 7;
            // 檢查前一個(gè)失活掛起的工作線程是否已經(jīng)復(fù)活
            if (r >= 0 && --spins == 0) {  // randomize spins
                WorkQueue v; WorkQueue[] ws; int s, j; AtomicLong sc;
                if (pred != 0 && (ws = workQueues) != null &&
                    (j = pred & SMASK) < ws.length &&
                    (v = ws[j]) != null &&        // see if pred parking
                    (v.parker == null || v.scanState >= 0))
                    spins = SPINS;                // continue spinning
            }
        }
        
        // 再次檢測(cè)隊(duì)列狀態(tài),是否被注銷
        else if (w.qlock < 0)    // recheck after spins
            return false;
        // 如果線程沒(méi)有被中斷
        else if (!Thread.interrupted()) {
            long c, prevctl, parkTime, deadline;
            // 獲取活躍線程數(shù)
            int ac = (int)((c = ctl) >> AC_SHIFT) + (config & SMASK);
            // 如果活躍線程數(shù)<=0,可能是要關(guān)閉線程池,這里會(huì)去幫忙關(guān)閉
            if ((ac <= 0 && tryTerminate(false, false)) ||
                (runState & STOP) != 0)           // pool terminating
                return false;
            // 如果活躍線程數(shù)<=0并且當(dāng)前線程是最后掛起的線程
            if (ac <= 0 && ss == (int)c) {        // is last waiter
                // 計(jì)算出一個(gè)ctl值
                prevctl = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & pred);
                // 獲取總線程數(shù)
                int t = (short)(c >>> TC_SHIFT);  // shrink excess spares
                // 如果總線數(shù)大于2,說(shuō)明掛起的線程已經(jīng)超過(guò)兩個(gè)了
                // 當(dāng)前線程會(huì)被拋棄
                if (t > 2 && U.compareAndSwapLong(this, CTL, c, prevctl))
                    // 返回false后,外面的runWorker()會(huì)直接break退出,
                    // 從而導(dǎo)致run()結(jié)束,線程死亡
                    return false;
                // 如果掛起的線程數(shù)<=2或者cas失敗(有線程被喚醒/復(fù)活)
                // 那么則計(jì)算掛起時(shí)間,將當(dāng)前線程掛起一段時(shí)間
                // 計(jì)算掛起時(shí)間
                parkTime = IDLE_TIMEOUT * ((t >= 0) ? 1 : 1 - t);
                // 計(jì)算結(jié)束時(shí)間
                deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP;
            }
            // 如果還存在活躍線程或當(dāng)前線程不是最后被掛起的線程
            else
                // 將當(dāng)前線程一直掛起(這類永久掛起的線程被喚醒后,如果對(duì)
                // 應(yīng)的scanState還是失活狀態(tài),這可能是線程池正在關(guān)閉了)
                prevctl = parkTime = deadline = 0L;
            // 獲取當(dāng)前線程
            Thread wt = Thread.currentThread();
            
            // 模仿LockSupport.park()掛起操作
            U.putObject(wt, PARKBLOCKER, this);   // emulate LockSupport
            w.parker = wt;
            // 掛起之前會(huì)再檢測(cè)一遍狀態(tài)
            if (w.scanState < 0 && ctl == c)      // recheck before park
                // 掛起操作
                U.park(false, parkTime);
            U.putOrderedObject(w, QPARKER, null);
            U.putObject(wt, PARKBLOCKER, null);
            // 如果被復(fù)活,則直接退出循環(huán),返回true
            if (w.scanState >= 0)
                break;
            // 如果阻塞時(shí)間不為零并且CTL值在期間沒(méi)有發(fā)生改變,那么說(shuō)明
            // 在這段時(shí)間內(nèi)外部并沒(méi)有提交新的任務(wù)進(jìn)來(lái),當(dāng)前線程則會(huì)被銷毀
            if (parkTime != 0L && ctl == c &&
                deadline - System.nanoTime() <= 0L &&
                U.compareAndSwapLong(this, CTL, c, prevctl))
                return false;                     // shrink pool
        }
    }
    return true;
}

先說(shuō)說(shuō)線程執(zhí)行任務(wù)的runTask()方法,在該方法中首先會(huì)修改狀態(tài)為執(zhí)行狀態(tài),執(zhí)行完成竊取到的任務(wù)之后會(huì)再執(zhí)行自身工作隊(duì)列中的任務(wù),在執(zhí)行自身任務(wù)時(shí),除非是指定成了FIFO模式,不然默認(rèn)都是會(huì)以LIFO模式,執(zhí)行完所有任務(wù)后,會(huì)將狀態(tài)重新改為掃描狀態(tài)。整體邏輯還算比較簡(jiǎn)單。

再來(lái)談?wù)剴炱?阻塞方法awaitWork,當(dāng)線程掃描不到任務(wù)時(shí),會(huì)先檢查自己是否需要自旋,如果需要?jiǎng)t會(huì)使用隨機(jī)種子配合實(shí)現(xiàn)隨機(jī)自旋。自旋結(jié)束后,如果池中掛起的(空閑的)線程數(shù)過(guò)多,或者外部已經(jīng)很久沒(méi)有提交新的任務(wù)進(jìn)來(lái),都會(huì)直接銷毀線程,從而達(dá)到縮減線程數(shù)的目的。

銷毀線程的實(shí)現(xiàn)也比較有意思,在前面的《線程池分析》中得知,線程池中的線程復(fù)用原理實(shí)則是通過(guò)死循環(huán)的方式卡住了run()方法,不讓run()方法結(jié)束,這樣線程就不會(huì)停止。而在該方法中,當(dāng)需要縮減線程數(shù)時(shí),則會(huì)直接返回false,讓外面的runWorker()方法中的循環(huán)退出,從而導(dǎo)致run()結(jié)束,讓線程正常執(zhí)行終止達(dá)到縮減線程數(shù)的目的。

二、任務(wù)的拆分與合并實(shí)現(xiàn)過(guò)程分析

在分析Fork/Join框架成員構(gòu)成時(shí),曾簡(jiǎn)單提到過(guò)fork/join()方法,下面再來(lái)詳細(xì)分解它兩的實(shí)現(xiàn)過(guò)程,先引用一下《上篇》中的片段:

// ForkJoinTask類 → fork方法
public final ForkJoinTask<V> fork() {
    Thread t;
    // 判斷當(dāng)前執(zhí)行的線程是否為池中的工作線程
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
        // 如果是的則直接將任務(wù)壓入當(dāng)前線程的任務(wù)隊(duì)列
        ((ForkJoinWorkerThread)t).workQueue.push(this);
    else
        // 如果不是則壓入common池中的某個(gè)工作線程的任務(wù)隊(duì)列中
        ForkJoinPool.common.externalPush(this);
    // 返回當(dāng)前ForkJoinTask對(duì)象,方便遞歸拆分
    return this;
}

// ForkJoinTask類 → join方法
public final V join() {
    int s;
    // 判斷任務(wù)執(zhí)行狀態(tài)如果是非正常結(jié)束狀態(tài)
    if ((s = doJoin() & DONE_MASK) != NORMAL)
        // 拋出相關(guān)的異常堆棧信息
        reportException(s);
    // 正常執(zhí)行結(jié)束則返回執(zhí)行結(jié)果
    return getRawResult();
}
// ForkJoinTask類 → doJoin方法
private int doJoin() {
    int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
    // status<0則直接返回status值
    return (s = status) < 0 ? s :
      // 判斷當(dāng)前線程是否為池中的工作線程
        ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
        // 是則取出線程任務(wù)隊(duì)列中的當(dāng)前task執(zhí)行,執(zhí)行完成返回status值
        (w = (wt = (ForkJoinWorkerThread)t).workQueue).
        // 嘗試將棧頂任務(wù)置空,然后執(zhí)行任務(wù)
        tryUnpush(this) && (s = doExec()) < 0 ? s :
        // 執(zhí)行未完成則調(diào)用awaitJoin方法等待執(zhí)行完成
        wt.pool.awaitJoin(w, this, 0L) :
      // 不是則調(diào)用externalAwaitDone()方法阻塞掛起當(dāng)前線程
      // 將任務(wù)交由通用的common線程池執(zhí)行
        externalAwaitDone();
}
  • fork方法邏輯:
    • ①判斷當(dāng)前線程是否為池中的工作線程類型
      • 是:將當(dāng)前任務(wù)壓入當(dāng)前線程的任務(wù)隊(duì)列中
      • 不是:將當(dāng)前任務(wù)壓入common池中某個(gè)工作線程的任務(wù)隊(duì)列中
    • ②返回當(dāng)前的ForkJoinTask任務(wù)對(duì)象,方便遞歸拆分
  • doJoin&join方法邏輯:
    • ①判斷任務(wù)狀態(tài)status是否小于0:
      • 小于:代表任務(wù)已經(jīng)結(jié)束,返回status值
      • 不小于:判斷當(dāng)前線程是否為池中的工作線程:
        • 是:嘗試從棧頂/隊(duì)尾取出當(dāng)前task執(zhí)行:
          • 任務(wù)在棧頂:執(zhí)行任務(wù)并返回執(zhí)行結(jié)束的status值
          • 不在棧頂:調(diào)用awaitJoin方法等待執(zhí)行結(jié)束
        • 不是:調(diào)用externalAwaitDone()方法阻塞掛起當(dāng)前線程,等待任務(wù)執(zhí)行結(jié)束
    • ②判斷任務(wù)執(zhí)行狀態(tài)是否為非正常結(jié)束狀態(tài),是則拋出異常堆棧信息
      • 任務(wù)狀態(tài)為被取消,拋出CancellationException異常
      • 任務(wù)狀態(tài)為異常結(jié)束,拋出對(duì)應(yīng)的執(zhí)行異常信息
    • ③如果status為正常結(jié)束狀態(tài),則直接返回執(zhí)行結(jié)果

關(guān)于doJoin方法的代碼可能看起來(lái)有些難理解,還是和前面上篇中分析“工作線程注冊(cè)的原理時(shí),理解奇數(shù)位索引計(jì)算”的方式一樣,自己寫一遍理解,換個(gè)寫法如下:

private int doJoin() {
    int s; Thread t; ForkJoinWorkerThread wt; 
    ForkJoinPool.WorkQueue w;
    // 如果任務(wù)已經(jīng)執(zhí)行完成,直接返回任務(wù)狀態(tài)
    if ((s = status) < 0) {
        return s;
    }
    t = Thread.currentThread();
    boolean isForkJoinThread = t instanceof ForkJoinWorkerThread;
    // 如果當(dāng)前線程不是工作線程,即外部線程直接調(diào)用join方法合并
    if (!isForkJoinThread) {
        // 等待任務(wù)被線程池分配線程執(zhí)行完,返回任務(wù)狀態(tài)
        return externalAwaitDone();
    }
    // 如果當(dāng)前線程是工作線程
    wt = (ForkJoinWorkerThread) t;
    w = wt.workQueue;
    // 如果當(dāng)前任務(wù)在隊(duì)列尾部/棧頂,直接彈出來(lái)
    if (w.tryUnpush(this)) {
        // 然后執(zhí)行彈出來(lái)的任務(wù)
        return this.doExec();
    }
    // 如果當(dāng)前任務(wù)不在隊(duì)列尾部/棧頂,那么調(diào)用awaitJoin等待
    return wt.pool.awaitJoin(w, this, 0L);
}

經(jīng)過(guò)這樣就可以非常清晰的看明白doJoin方法的邏輯啦。接著往下分析,其實(shí)fork的原理實(shí)現(xiàn)還算簡(jiǎn)單,下面重點(diǎn)分析join的實(shí)現(xiàn)。先看看tryUnpush()方法:

// ForkJoinTask類 → tryUnpush()方法
final boolean tryUnpush(ForkJoinTask<?> t) {
    ForkJoinTask<?>[] a; int s;
    // 嘗試將棧頂/隊(duì)尾任務(wù)置空,如果t就是隊(duì)列中的棧頂任務(wù),那嘗試cas置空
    if ((a = array) != null && (s = top) != base &&
        U.compareAndSwapObject
        (a, (((a.length - 1) & --s) << ASHIFT) + ABASE, t, null)) {
        U.putOrderedInt(this, QTOP, s);
        return true;
    }
    return false;
}

工作線程在合并結(jié)果時(shí),如果這個(gè)任務(wù)被fork到了棧頂/隊(duì)尾,那么執(zhí)行該任務(wù)返回即可。但如果不在棧頂,有可能是被其他fork出的任務(wù)壓下去了或者其他線程被竊取了,那么則會(huì)進(jìn)入awaitJoin()方法。

2.1、awaitJoin方法

接著來(lái)看看awaitJoin()方法,源碼如下:

// ForkJoinTask類 → awaitJoin()方法
final int awaitJoin(WorkQueue w, ForkJoinTask<?> task, long deadline) {
    int s = 0;
    if (task != null && w != null) {
        // 記錄前一個(gè)正在合并的任務(wù)
        ForkJoinTask<?> prevJoin = w.currentJoin;
        // 記錄join合并當(dāng)前任務(wù)
        U.putOrderedObject(w, QCURRENTJOIN, task);
        // CountedCompleter是ForkJoinTask的一個(gè)子類實(shí)現(xiàn)
        CountedCompleter<?> cc = (task instanceof CountedCompleter) ?
            (CountedCompleter<?>)task : null;
        // 自旋操作
        for (;;) {
            // 1.任務(wù)已經(jīng)執(zhí)行完畢,不需要再自旋了,直接返回
            if ((s = task.status) < 0)
                break;
            // 如果任務(wù)是CountedCompleter類型,則獲取它的派生子任務(wù)執(zhí)行
            if (cc != null)
                helpComplete(w, cc, 0);
            // 如果隊(duì)列不為空,嘗試從隊(duì)列中獲取當(dāng)前需要join的任務(wù)執(zhí)行。
            // 如果當(dāng)前隊(duì)列任務(wù)為空,說(shuō)明當(dāng)前任務(wù)被其他工作線程給竊取了
            // tryRemoveAndExec是用于嘗試執(zhí)行存到隊(duì)列中的當(dāng)前任務(wù),
            // 如果隊(duì)列中沒(méi)有找到當(dāng)前join的任務(wù),那代表被其他線程給偷走了
            else if (w.base == w.top || w.tryRemoveAndExec(task))
                // 找到竊取join任務(wù)的工作線程,幫助竊取者執(zhí)行竊取者的任務(wù)
                helpStealer(w, task);
            
            // 3.再判斷一次任務(wù)是否已經(jīng)執(zhí)行完畢,執(zhí)行結(jié)束則退出
            // 如果任務(wù)被竊取,能夠執(zhí)行到這一步,那么一定是前面的
            // helpStealer方法退出了,原因有兩個(gè):
            //      1.自己需要join合并的任務(wù)執(zhí)行完了
            //      2.竊取鏈斷了或沒(méi)有可竊取的任務(wù)了,準(zhǔn)備阻塞
            if ((s = task.status) < 0)
                break;
            long ms, ns;
            if (deadline == 0L)
                ms = 0L;
            else if ((ns = deadline - System.nanoTime()) <= 0L)
                break;
            else if ((ms = TimeUnit.NANOSECONDS.toMillis(ns)) <= 0L)
                ms = 1L;
            // 4.調(diào)用tryCompensate方法對(duì)線程池進(jìn)行補(bǔ)償
            // 進(jìn)入阻塞之前為了避免線程池所有線程都進(jìn)入阻塞,
            // 會(huì)為線程池補(bǔ)償一個(gè)活躍線程(喚醒或新建)
            if (tryCompensate(w)) {
                // 自旋加阻塞,等待其他線程執(zhí)行完成竊取的join任務(wù)
                task.internalWait(ms);
                // 喚醒后疊加活躍線程數(shù)
                U.getAndAddLong(this, CTL, AC_UNIT);
            }
        }
        // 當(dāng)任務(wù)執(zhí)行完成后,將currentJoin恢復(fù)成之前的currentJoin值
        U.putOrderedObject(w, QCURRENTJOIN, prevJoin);
    }
    return s;
}
// ForkJoinTask類 → tryRemoveAndExec()方法
final boolean tryRemoveAndExec(ForkJoinTask<?> task) {
    ForkJoinTask<?>[] a; int m, s, b, n;
    // 如果隊(duì)列中的任務(wù)數(shù)組不為空且已經(jīng)初始化
    if ((a = array) != null && (m = a.length - 1) >= 0 &&
        task != null) {
        // 隊(duì)列中是否存在任務(wù)
        while ((n = (s = top) - (b = base)) > 0) {
            for (ForkJoinTask<?> t;;) {     // traverse from s to b
                // 從棧頂開始往下取值
                long j = ((--s & m) << ASHIFT) + ABASE;
                // 因?yàn)榇嬖诓l(fā),可能會(huì)被竊取者偷走任務(wù)
                if ((t = (ForkJoinTask<?>)U.getObject(a, j)) == null)
                    // 如果發(fā)生了任務(wù)竊取,那說(shuō)明此時(shí)的s已經(jīng)執(zhí)行到了棧底
                    // 如果被偷走join任務(wù)是在棧頂被偷走的,那么將返回true
                    return s + 1 == top;     // shorter than expected
                // 如果找到了任務(wù)
                else if (t == task) {
                    boolean removed = false;
                    // 當(dāng)前join任務(wù)在棧頂,嘗試將其彈出
                    // 如果cas失敗,代表被其他線程偷走,此時(shí)隊(duì)列已經(jīng)空了
                    if (s + 1 == top) {      // pop
                        if (U.compareAndSwapObject(a, j, task, null)) {
                            U.putOrderedInt(this, QTOP, s);
                            removed = true;
                        }
                    }
                    // 當(dāng)前join任務(wù)不在棧頂并且棧底沒(méi)變,
                    // 將當(dāng)前join任務(wù)的坑位替換成EmptyTask對(duì)象
                    else if (base == b)      // replace with proxy
                        // 因?yàn)槿蝿?wù)不在棧頂,不能直接替換成null,
                        // 替換成null就必須移動(dòng)指針,顯然這里不能移動(dòng)指針
                        // 很多地方都是以null作為并發(fā)判斷,
                        // 其他工作線程取到null時(shí),
                        // 會(huì)認(rèn)為任務(wù)被其他線程竊取了任務(wù),
                        // 這樣就永遠(yuǎn)獲取不到任務(wù)了
                        removed = U.compareAndSwapObject(
                            a, j, task, new EmptyTask());
                    if (removed)
                        //執(zhí)行任務(wù)
                        task.doExec();
                    break;
                }
                // 如果其他任務(wù)已經(jīng)執(zhí)行完成,并且是棧頂任務(wù),那么置空
                else if (t.status < 0 && s + 1 == top) {
                    if (U.compareAndSwapObject(a, j, t, null))
                        U.putOrderedInt(this, QTOP, s);
                    break;                  // was cancelled
                }
                // 從棧頂找到棧底都沒(méi)有找到,返回false
                // 雖然任務(wù)被偷了,但是也不去參與helpStealer了
                if (--n == 0)
                    return false;
            }
            // 任務(wù)已經(jīng)完成
            if (task.status < 0)
                return false;
        }
    }
    return true;
}

awaitJoin方法的總體邏輯還算簡(jiǎn)單,如下:

  • ①檢查當(dāng)前線程的工作隊(duì)列是否為空
    • 為空:代表任務(wù)被竊取了
    • 不為空:通過(guò)tryRemoveAndExec在整個(gè)隊(duì)列中查找當(dāng)前需要join的任務(wù)
      • 找到了:執(zhí)行任務(wù)
      • 沒(méi)找到:代表任務(wù)還是被偷了(這種情況下不參與helpStealer方法)
  • ②如果任務(wù)被偷了,那么通過(guò)helpStealer找到竊取者,幫助它執(zhí)行任務(wù)
  • ③如果從helpStealer方法中退出,會(huì)再檢查一次任務(wù)是否已完成:
    • 已執(zhí)行結(jié)束:退出循環(huán),出去合并結(jié)果
    • 未執(zhí)行結(jié)束:準(zhǔn)備進(jìn)入阻塞,避免CPU資源浪費(fèi)
  • ④在進(jìn)入阻塞之前,會(huì)先對(duì)線程池進(jìn)行補(bǔ)償,因?yàn)楫?dāng)前線程可能是線程池中的最后一個(gè)活躍線程,為了避免線程池所有線程都“死”掉,會(huì)先為線程池補(bǔ)償一個(gè)活躍線程

ok~,再來(lái)看看tryRemoveAndExec方法的邏輯,如下:

  • 判斷隊(duì)列中是否有任務(wù):
    • 不存在:返回true,外部的awaitJoin方法進(jìn)入helpStealer邏輯
    • 存在:判斷任務(wù)是否在隊(duì)列尾部/棧底:
      • 在:嘗試CAS彈出棧頂任務(wù):
        • 成功:執(zhí)行任務(wù)
        • 失敗:代表CAS失敗,任務(wù)被別的線程偷走了,進(jìn)入helpStealer邏輯
      • 不在:可能被其他任務(wù)壓下去了,從棧頂開始查找整個(gè)隊(duì)列:
        • 找到了:將任務(wù)替換成EmptyTask對(duì)象,執(zhí)行任務(wù)
        • 沒(méi)找到:代表任務(wù)被偷了,但雖然沒(méi)找到,也不參與helpStealer了,不過(guò)在退出之前會(huì)再一次檢測(cè)任務(wù)是否執(zhí)行完成

tryRemoveAndExec方法比較簡(jiǎn)單,該方法主要作用是遍歷當(dāng)前線程的WorkQueue,在隊(duì)列中查找要join合并的任務(wù)執(zhí)行。而在執(zhí)行過(guò)程中,如果隊(duì)列為空或者任務(wù)在棧頂?shù)玞as失敗以及遍歷完整個(gè)隊(duì)列都沒(méi)找到要join的任務(wù),這三種情況代表任務(wù)被偷了,對(duì)于前兩種情況下,會(huì)進(jìn)入helpStealer幫助竊取者執(zhí)行任務(wù),而對(duì)于最后一種被竊取任務(wù)的情況,則會(huì)直接退出阻塞(個(gè)人猜測(cè):可能是因?yàn)楸闅v完整個(gè)隊(duì)列會(huì)導(dǎo)致一段時(shí)間的開銷,被竊取走的任務(wù)很有可能在這段時(shí)間內(nèi)已經(jīng)執(zhí)行完了或快執(zhí)行完了。所以與其去幫助竊取者執(zhí)行任務(wù),還不如阻塞等待一會(huì)兒)。

2.2、helpStealer幫助竊取者執(zhí)行方法

再來(lái)看看helpStealer方法,源碼如下:

// ForkJoinTask類 → helpStealer()方法
private void helpStealer(WorkQueue w, ForkJoinTask<?> task) {
    WorkQueue[] ws = workQueues;
    int oldSum = 0, checkSum, m;
    // 如果隊(duì)列數(shù)組和任務(wù)隊(duì)列不為空
    if (ws != null && (m = ws.length - 1) >= 0 && w != null &&
        task != null) {
        do {                    // restart point
            checkSum = 0;      // for stability check
            ForkJoinTask<?> subtask;
            WorkQueue j = w, v;   // v is subtask stealer
            descent: for (subtask = task; subtask.status >= 0; ) {
                // j.hint開始是j隊(duì)列在隊(duì)列組中的用于計(jì)算下標(biāo)的隨機(jī)值,
                // 如果找到了竊取者,這個(gè)值會(huì)變成對(duì)應(yīng)竊取者的下標(biāo)
                // j.hint | 1=一個(gè)奇數(shù),k += 2:步長(zhǎng)為2,奇數(shù)+2=奇數(shù)
                for (int h = j.hint | 1, k = 0, i; ; k += 2) {
                    // 查找完整個(gè)對(duì)應(yīng)數(shù)組的所有奇數(shù)位,
                    // 如果還是沒(méi)有找到任務(wù),則直接退出(可能執(zhí)行完成了)
                    if (k > m)        // can't find stealer
                        break descent;
                    //(h + k) & m:計(jì)算出一個(gè)數(shù)組之內(nèi)的奇數(shù)下標(biāo),
                    // 檢查這個(gè)下標(biāo)的隊(duì)列是否偷走了自己的任務(wù)
                    if ((v = ws[i = (h + k) & m]) != null) {
                        // 判斷currentSteal是否是當(dāng)前的任務(wù)
                        if (v.currentSteal == subtask) {
                            // 是的,記錄這個(gè)偷取者在隊(duì)列組的下標(biāo)
                            j.hint = i;
                            break;
                        }
                        // 檢查了一個(gè)隊(duì)列之后會(huì)計(jì)入校驗(yàn)和
                        checkSum += v.base;
                    }
                }
                // 當(dāng)前線程幫竊取者線程執(zhí)行任務(wù)
                for (;;) {           // help v or descend
                    ForkJoinTask<?>[] a; int b;
                    // 將竊取者線程隊(duì)列棧底也計(jì)入校驗(yàn)和,因?yàn)樗`取了任務(wù)
                    // ,很有可能fork出更小的任務(wù)然后被其他線程偷走
                    checkSum += (b = v.base);
                    // 獲取竊取者當(dāng)前正在join的任務(wù)
                    ForkJoinTask<?> next = v.currentJoin;
                    //subtask.status < 0 任務(wù)執(zhí)行完成
                    // 如果任務(wù)執(zhí)行結(jié)果
                    //      或者工作線程要合并的任務(wù)已經(jīng)不是subtask了
                    //      或者竊取者竊取的任務(wù)已經(jīng)不為當(dāng)前join任務(wù)了
                    // 那么退出循環(huán)
                    if (subtask.status < 0 || j.currentJoin != subtask ||
                        v.currentSteal != subtask) // stale
                        break descent;
                    // 如果當(dāng)前線程中沒(méi)有任務(wù),則會(huì)幫它join合并任務(wù)
                    // 在這里會(huì)對(duì)subtask重新賦值,如果為空則會(huì)回到descent
                    // 循環(huán)進(jìn)行下一個(gè)迭代
                    if (b - v.top >= 0 || (a = v.array) == null) {
                        // 如果竊取者不需要join合并任務(wù),
                        // 退出判斷任務(wù)是否結(jié)束
                        if ((subtask = next) == null)
                            break descent;
                        // 如果竊取者有任務(wù)要join合并,
                        // 那將幫竊取者去找偷它任務(wù)的竊取者
                        j = v;
                        break;
                    }
                    // 如果竊取者的隊(duì)列中有任務(wù),從棧底/隊(duì)頭開始偷竊取者
                    // 線程的任務(wù)執(zhí)行(可能竊取到自身被偷的任務(wù)
                    // fork出來(lái)的子任務(wù)),
                    int i = (((a.length - 1) & b) << ASHIFT) + ABASE;
                    ForkJoinTask<?> t = ((ForkJoinTask<?>)
                                         U.getObjectVolatile(a, i));
                    // 偷完之后看一下棧底/隊(duì)頭有沒(méi)有發(fā)生變化,
                    // 如果變了,代表有其他線程也在偷竊取者線程的任務(wù),
                    // 避免無(wú)效的cas,直接重新再偷一個(gè)新的任務(wù)
                    if (v.base == b) {
                        // ==null,代表任務(wù)被其他線程偷了,
                        // 然后賦值成了null,只是還沒(méi)來(lái)得及將base更新
                        if (t == null)             // stale
                            // 回到 descent 標(biāo)志進(jìn)行下一個(gè)迭代
                            break descent;
                        // 如果沒(méi)變則cas置空棧底/隊(duì)頭的任務(wù)
                        // 這樣可以告訴別的線程當(dāng)前任務(wù)已被竊取
                        if (U.compareAndSwapObject(a, i, t, null)) {
                            // 更新棧底指針
                            v.base = b + 1;
                            // 記錄自己前一個(gè)偷取的任務(wù)
                            ForkJoinTask<?> ps = w.currentSteal;
                            int top = w.top;
                            do {
                                // 將新偷到的任務(wù)更新到currentSteal中
                                U.putOrderedObject(w, QCURRENTSTEAL, t);
                                // 執(zhí)行竊取到的任務(wù)
                                t.doExec();  // clear local tasks too
                                // 在join的任務(wù)還未執(zhí)行完成的情況下,
                                // 并且剛才執(zhí)行的任務(wù)發(fā)生了fork任務(wù),
                                // 那么w.top !=top就會(huì)成立,
                                // 此時(shí)就得w.pop()執(zhí)行本地任務(wù)
                            } while (task.status >= 0 &&
                                     w.top != top &&
                                     (t = w.pop()) != null);
                            // 執(zhí)行結(jié)束后恢復(fù)原本的竊取記錄
                            U.putOrderedObject(w, QCURRENTSTEAL, ps);
                            // 然后再看看自身隊(duì)列中有沒(méi)有任務(wù)
                            // 如果w.base != w.top成立,代表自身隊(duì)列來(lái)了
                            // 任務(wù),此時(shí)則直接結(jié)束,回去執(zhí)行自己的任務(wù),
                            // 沒(méi)有必要幫別的線程執(zhí)行任務(wù)了
                            if (w.base != w.top)
                                return;     // can't further help
                        }
                    }
                }
            }
            // 退出helpStealer的條件有兩個(gè):
            // 1.自己需要合并的join任務(wù)執(zhí)行完了,回去執(zhí)行自己的合并任務(wù);
            // 2.自己的join任務(wù)沒(méi)執(zhí)行完,但已經(jīng)竊取不到任務(wù)了,那退出阻塞
            //    當(dāng)前線程,因?yàn)槔^續(xù)找下去也是空跑,浪費(fèi)CPU資源
        } while (task.status >= 0 && oldSum != (oldSum = checkSum));
    }
}

該方法是ForkJoin框架實(shí)現(xiàn)“工作竊取思想”的核心體現(xiàn)。它與scan掃描方法完成了整個(gè)框架“工作竊取”實(shí)現(xiàn)。在scan方法之后的runTask方法中,會(huì)對(duì)currentSteal賦值,而helpStealer方法就是依賴于該成員與currentJoin成員形成的一條竊取鏈,實(shí)現(xiàn)了幫助竊取者執(zhí)行任務(wù),關(guān)于helpStealer的具體邏輯則不再分析了,大家可以參考上述源碼中的注釋。

總而言之,helpStealer方法的核心思想是幫助執(zhí)行,幫助竊取者執(zhí)行它的任務(wù),但它不僅僅只會(huì)幫助竊取者執(zhí)行,還會(huì)基于currentStealcurrentJoin成員形成的竊取鏈幫助竊取者的竊取者執(zhí)行、幫助竊取者的竊取者的竊取者執(zhí)行、幫助竊取者.....的竊取者執(zhí)行任務(wù)。上個(gè)例子理解,如下:

  • ①線程T1需要join合并任務(wù)TaskA,但是TaskA被偷了,開始遍歷查找所有奇數(shù)隊(duì)列
  • ②查找后發(fā)現(xiàn)TaskA==線程T2.currentSteal成員,此時(shí)T2為T1的竊取者
  • ③T1從T2的隊(duì)列棧底竊取一個(gè)任務(wù)執(zhí)行,執(zhí)行完再竊取一個(gè)執(zhí)行,繼續(xù)竊取....
  • ④T1發(fā)現(xiàn)T2的隊(duì)列中沒(méi)有了任務(wù),T1則會(huì)繼續(xù)尋找竊取了T2.currentlJoin的線程
  • ⑤經(jīng)過(guò)遍歷發(fā)現(xiàn)T2.currentlJoin==T5.currentSteal,T5為T2的竊取者
  • ⑥然后T1繼續(xù)從T5隊(duì)列的棧底竊取一個(gè)任務(wù)執(zhí)行,完成后繼續(xù)竊取.....
  • ⑦T1發(fā)現(xiàn)T5的隊(duì)列中也沒(méi)有了任務(wù),T1會(huì)繼續(xù)尋找竊取了T5.currentlJoin的....
  • ⑧根據(jù)竊取鏈,一直這樣循環(huán)下去.....

通過(guò)如上過(guò)程可發(fā)現(xiàn):T1.currentlJoin → T2.currentSteal → T2.currentlJoin → T5.currentSteal → T5.currentlJoin....,通過(guò)currentStealcurrentJoin兩個(gè)成員構(gòu)成了一條竊取鏈,如果理解了這條鏈路關(guān)系,那么也就理解了helpStealer方法。不過(guò)值得注意的是:helpStealer方法什么時(shí)候退出呢?答案是:竊取鏈斷掉的時(shí)候會(huì)退出。總共有三種情況會(huì)導(dǎo)致竊取鏈斷掉:

  • ①任何一個(gè)工作線程的currentStealcurrentJoin為空
  • ②任何一個(gè)工作線程的currentStealcurrentJoin已經(jīng)執(zhí)行完成
  • ③當(dāng)前線程的join任務(wù)已經(jīng)執(zhí)行完成

其實(shí)說(shuō)到底,helpStealer方法是ForkJoin框架的一個(gè)優(yōu)化性能的實(shí)現(xiàn)點(diǎn),核心點(diǎn)在于減少線程因?yàn)楹喜⒍枞诘却齤oin任務(wù)執(zhí)行期間幫其它線程執(zhí)行一個(gè)任務(wù),這樣則保證了每個(gè)線程不停止工作,也能夠加快整體框架的處理速度,同時(shí)在幫助執(zhí)行的期間,被竊取的join任務(wù)就執(zhí)行完了。

2.3、tryCompensate補(bǔ)償活躍線程方法

再來(lái)看看為線程池補(bǔ)償活躍線程的tryCompensate方法:

// ForkJoinPool類 → tryCompensate()方法
private boolean tryCompensate(WorkQueue w) {
    boolean canBlock;
    WorkQueue[] ws; long c; int m, pc, sp;
    // 如果線程池已經(jīng)停止,處于terminate狀態(tài),不能阻塞,也不需要阻塞
    if (w == null || w.qlock < 0 ||           // caller terminating
        (ws = workQueues) == null || (m = ws.length - 1) <= 0 ||
        (pc = config & SMASK) == 0)           // parallelism disabled
        canBlock = false;
    // 如果ctl的低32位中有掛起的空閑線程,那么嘗試喚醒它,成功則阻塞自己
    // 喚醒后在一定程度上也許會(huì)執(zhí)行到自己被偷的任務(wù)fork出的子任務(wù)
    // tryRelease第二個(gè)參數(shù)為0,當(dāng)喚醒成功后,代表當(dāng)前線程將被阻塞,
    // 新的空閑線程被喚醒,所以沒(méi)必要先減少活躍線程數(shù),然后再加上
    else if ((sp = (int)(c = ctl)) != 0)      // release idle worker
        canBlock = tryRelease(c, ws[sp & m], 0L);
    // 如果沒(méi)有空閑線程,就要?jiǎng)?chuàng)建新的線程
    // 這里會(huì)導(dǎo)致線程池中的線程數(shù),在一段時(shí)間內(nèi)會(huì)超過(guò)創(chuàng)建時(shí)指定的并行數(shù)
    else {
        // 獲取池中的活躍線程數(shù)
        int ac = (int)(c >> AC_SHIFT) + pc;
        // 獲取池中的總線程數(shù)
        int tc = (short)(c >> TC_SHIFT) + pc;
        int nbusy = 0;       // validate saturation
        for (int i = 0; i <= m; ++i) {  // two passes of odd indices
            WorkQueue v;
            // 找奇數(shù)位置的隊(duì)列,循環(huán)m次就是執(zhí)行了兩遍。
            // 為什么執(zhí)行兩遍呢?主要是為了判斷穩(wěn)定性,有可能第二遍
            //  的時(shí)候,正在執(zhí)行任務(wù)的活躍線程會(huì)變少
            if ((v = ws[((i << 1) | 1) & m]) != null) {
                // 檢查工作線程是否正在處理任務(wù),
                // 如果不在處理任務(wù)表示空閑,可以獲取其他任務(wù)執(zhí)行
                if ((v.scanState & SCANNING) != 0)
                    break;
                ++nbusy;
            }
        }
        // 如果線程池狀態(tài)不穩(wěn)定,那么則不能掛起當(dāng)前線程
        // 如果nbusy!=tc*2 說(shuō)明還存在空閑或者還在掃描任務(wù)的工作線程
        // 如果ctl!=c 代表ctl發(fā)生了改變,有可能線程執(zhí)行完任務(wù)后,
        // 沒(méi)有掃描到新的任務(wù)被失活,這種情況下先不掛起,先自旋一段時(shí)間
        if (nbusy != (tc << 1) || ctl != c)
            canBlock = false;         // unstable or stale
        
        // tc:池內(nèi)總線程數(shù)  pc:并行數(shù) ac:池內(nèi)活躍線程數(shù)
        // tc>=pc 代表此時(shí)線程數(shù)已經(jīng)夠多了,當(dāng)然并不代表不會(huì)創(chuàng)建新線程
        // ac>1 代表除了自己外還有其他活躍線程
        // w.isEmpty() 當(dāng)前工作線程隊(duì)列為空,其中沒(méi)有任務(wù)需要執(zhí)行
        // 如果滿足如上三個(gè)條件,那么則可以直接阻塞,不需要補(bǔ)償
        else if (tc >= pc && ac > 1 && w.isEmpty()) {
            long nc = ((AC_MASK & (c - AC_UNIT)) |
                       (~AC_MASK & c));      // uncompensated
            //cas ctl
            canBlock = U.compareAndSwapLong(this, CTL, c, nc);
        }
        // 這是對(duì)于commonPool 公共線程池的特殊處理
        // 如果總線程數(shù)超出MAX_CAP則會(huì)拋出異常
        else if (tc >= MAX_CAP ||
                 (this == common && tc >= pc + commonMaxSpares))
            throw new RejectedExecutionException(
                "Thread limit exceeded replacing blocked worker");
        else {                                // similar to tryAddWorker
            boolean add = false; int rs;      // CAS within lock
            // 準(zhǔn)備創(chuàng)建新的工作線程(這里只加總線程數(shù),不加活躍線程數(shù))
            //      因?yàn)楫?dāng)前工作線程將在創(chuàng)建補(bǔ)償線程成功之后阻塞
            // 但是這里會(huì)導(dǎo)致總線程數(shù)超出并行數(shù)
            long nc = ((AC_MASK & c) |
                       (TC_MASK & (c + TC_UNIT)));
            // 線程池沒(méi)有停止的情況下才允許創(chuàng)建新的工作線程
            if (((rs = lockRunState()) & STOP) == 0)
                add = U.compareAndSwapLong(this, CTL, c, nc);
            unlockRunState(rs, rs & ~RSLOCK);
            // 創(chuàng)建新的工作線程
            canBlock = add && createWorker(); // throws on exception
        }
    }
    return canBlock;
}

該方法內(nèi)的邏輯也算比較簡(jiǎn)單:

  • ①判斷池內(nèi)有沒(méi)有掛起的空閑線程,如果有則喚醒它代替自己
  • ②如果沒(méi)有掛起的空閑線程,判斷池內(nèi)活躍線程數(shù)是否存在兩個(gè)及以上、總線程數(shù)是否飽和、自己工作隊(duì)列是否為空,如果這些都滿足,那么則不需要補(bǔ)償,直接掛起
  • ③如果不滿足上述三條件,判斷線程數(shù)是否關(guān)閉,如果沒(méi)有則創(chuàng)建新線程補(bǔ)償

值得一提的是:tryCompensate方法會(huì)導(dǎo)致一段時(shí)間內(nèi),池中總線程數(shù)超出創(chuàng)建線程池時(shí)指定的并行數(shù)。而且如果在用Fork/Join框架時(shí),如果在ForkJoinTask中調(diào)用提交任務(wù)的方法:sumbit()/invoke()/execute()時(shí),會(huì)導(dǎo)致線程池一直補(bǔ)償線程,硬件允許的情況下會(huì)導(dǎo)致一直補(bǔ)償創(chuàng)建出最大0x7fff = 32767條線程。

2.4、externalAwaitDone方法

前面分析doJoin邏輯提到過(guò):如果是外部線程調(diào)用join方法時(shí),會(huì)調(diào)用externalAwaitDone方法,接著再來(lái)看看這個(gè)方法:

// ForkJoinPool類 → externalAwaitDone()方法
private int externalAwaitDone() {
    // 如果任務(wù)是CountedCompleter類型,嘗試使用common池去外部幫助執(zhí)行,
    // 執(zhí)行完成后并將完成任務(wù)狀態(tài)返回
    int s = ((this instanceof CountedCompleter) ? // try helping
             ForkJoinPool.common.externalHelpComplete(
                 (CountedCompleter<?>)this, 0) :
                 // 當(dāng)前task不是CountedCompleter,嘗試從棧頂獲取到當(dāng)前
                 // join的任務(wù)交給common池執(zhí)行,如果不在棧頂,s變?yōu)?
             ForkJoinPool.common.tryExternalUnpush(this) ? doExec() : 0);
    // 如果s>=0,那代表任務(wù)是未結(jié)束的狀態(tài),需要阻塞
    if (s >= 0 && (s = status) >= 0) {
        boolean interrupted = false;
        do {
            // 先設(shè)置SIGNAL信號(hào)標(biāo)記,通知其他線程當(dāng)前需要被喚醒
            if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) {
                // 通過(guò)synchronized.wait()掛起線程
                synchronized (this) {
                    if (status >= 0) { // 雙重檢測(cè)
                        try {
                            wait(0L);   // 掛起線程
                        } catch (InterruptedException ie) {
                            interrupted = true;
                        }
                    }
                    else
                        // 如果發(fā)現(xiàn)已完成,則喚醒所有等待線程
                        notifyAll();
                }
            }
        // task未完成會(huì)一直循環(huán)
        } while ((s = status) >= 0);
        // 響應(yīng)中斷操作
        if (interrupted)
            Thread.currentThread().interrupt();
    }
    // 執(zhí)行完成后返回執(zhí)行狀態(tài)
    return s;
}

externalAwaitDone方法最簡(jiǎn)單,如果任務(wù)在棧頂,那么直接彈出執(zhí)行,如果不在則掛起當(dāng)前線程,直至任務(wù)執(zhí)行結(jié)束,其他線程喚醒。

2.5、任務(wù)拆分合并原理總結(jié)

任務(wù)的fork操作比較簡(jiǎn)單,只需要將拆分好的任務(wù)push進(jìn)入自己的工作隊(duì)列即可。而對(duì)于任務(wù)結(jié)果的合并:join操作,實(shí)現(xiàn)就略顯復(fù)雜了,大體思想是首先在自己隊(duì)列中找需要join的任務(wù),如果找到了則執(zhí)行它并合并結(jié)果。如果沒(méi)找到就是被偷了,需要去找竊取者線程,并且在join任務(wù)執(zhí)行結(jié)束之前,會(huì)根據(jù)竊取鏈一直幫助竊取者執(zhí)行任務(wù),如果竊取鏈斷了但是join任務(wù)還未執(zhí)行完,那么掛起當(dāng)前工作線程,不過(guò)在掛起之前會(huì)根據(jù)情況來(lái)決定是否為線程池補(bǔ)償一條活躍線程代替自己工作,防止整個(gè)線程池所有的線程都阻塞,產(chǎn)生線程池“假死”狀態(tài)。當(dāng)然,如果是外部線程執(zhí)行的join操作,如果要被join的任務(wù)還未執(zhí)行完的情況下,那么則需要把這任務(wù)交給commonPool公共池來(lái)處理。

三、ForkJoin中任務(wù)取消實(shí)現(xiàn)原理

任務(wù)取消的cancel方法是實(shí)現(xiàn)于Future接口的,邏輯比較簡(jiǎn)單,源碼如下:

// ForkJoinTask類 → cancel()方法
public boolean cancel(boolean mayInterruptIfRunning) {
    // 嘗試將任務(wù)狀態(tài)修改為CANCELLED,成功返回true,失敗返回false
    return (setCompletion(CANCELLED) & DONE_MASK) == CANCELLED;
}

// ForkJoinTask類 → setCompletion()方法
private int setCompletion(int completion) {
    // 開啟自旋(死循環(huán))
    for (int s;;) {
        // 如果任務(wù)已經(jīng)完成,則直接返回執(zhí)行后的狀態(tài)
        if ((s = status) < 0)
            return s;
        // 如果還未完成則嘗試通過(guò)cas機(jī)制修改狀態(tài)為入?yún)ⅲ篶ompletion狀態(tài)
        if (U.compareAndSwapInt(this, STATUS, s, s | completion)) {
            if ((s >>> 16) != 0)
                synchronized (this) { notifyAll(); }
            return completion;
        }
    }
}

取消任務(wù)的邏輯比較簡(jiǎn)單,任務(wù)取消只能發(fā)生在任務(wù)還未被執(zhí)行的情況下,如果任務(wù)已經(jīng)完成則會(huì)直接返回執(zhí)行狀態(tài)。如果任務(wù)還未執(zhí)行,則會(huì)嘗試使用自旋+CAS機(jī)制修改任務(wù)狀態(tài)為CANCELLED狀態(tài),成功則代表任務(wù)取消成功。

四、ForkJoinPool線程池的關(guān)閉實(shí)現(xiàn)

一般在正常關(guān)閉線程池時(shí),都會(huì)通過(guò)shundown方法來(lái)停止線程池,接著再分析一下線程池關(guān)閉的實(shí)現(xiàn):

// ForkJoinPool類 → shutdown()方法
public void shutdown() {
    // 檢查權(quán)限
    checkPermission();
    // 關(guān)閉線程池
    tryTerminate(false, true);
}

// ForkJoinPool類 → checkPermission()方法
private static void checkPermission() {
    // 獲取權(quán)限管理器
    SecurityManager security = System.getSecurityManager();
    // 檢測(cè)當(dāng)前線程是否具備關(guān)閉線程池的權(quán)限
    if (security != null)
        security.checkPermission(modifyThreadPermission);
}

// ForkJoinPool類 → tryTerminate()方法
private boolean tryTerminate(boolean now, boolean enable) {
    int rs;
    // 如果是common公開池,不能關(guān)閉,common的關(guān)閉和Java程序綁定
    if (this == common)            // cannot shut down
        return false;
    // 如果線程池還在運(yùn)行,那么檢測(cè)enable是否為true,如果是false則退出
    if ((rs = runState) >= 0) {
        if (!enable)
            return false;
        rs = lockRunState();                  // enter SHUTDOWN phase
        // 如果線程池是要關(guān)閉,首先把運(yùn)行狀態(tài)改為 SHUTDOWN 標(biāo)記
        unlockRunState(rs, (rs & ~RSLOCK) | SHUTDOWN);
    }
    // 如果線程池還不是stop停止?fàn)顟B(tài)(rs&stop==1表示處于stop狀態(tài))
    if ((rs & STOP) == 0) {
        // 如果now入?yún)閒alse會(huì)進(jìn)入如下邏輯
        if (!now) {                 // check quiescence
            // 遍歷整個(gè)工作隊(duì)列數(shù)組
            for (long oldSum = 0L;;) {        // repeat until stable
                WorkQueue[] ws; WorkQueue w; int m, b; long c;
                // 以目前的ctl值作為初始效驗(yàn)和
                long checkSum = ctl;
                // 檢測(cè)池內(nèi)活躍線程數(shù),如果>0則不能直接置為stop狀態(tài)
                if ((int)(checkSum >> AC_SHIFT) + (config & SMASK) > 0)
                    return false;             // still active workers
                // 如果工作隊(duì)列全部被注銷了則可以設(shè)置為stop狀態(tài)
                if ((ws = workQueues) == null || (m = ws.length - 1) <= 0)
                    break;                    // check queues
                // 開啟循環(huán)
                for (int i = 0; i <= m; ++i) {
                    // 循環(huán)每個(gè)工作隊(duì)列
                    if ((w = ws[i]) != null) {
                        // 如果隊(duì)列中還存在任務(wù),且當(dāng)前隊(duì)列處于活躍狀態(tài)
                        if ((b = w.base) != w.top || w.scanState >= 0 ||
                            w.currentSteal != null) {
                            // 喚醒空閑的線程幫助執(zhí)行還未處理的任務(wù)
                            tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
                            return false;     // arrange for recheck
                        }
                        // 以棧底作為校驗(yàn)和
                        checkSum += b;
                        // 將偶數(shù)位隊(duì)列中的任務(wù)全部取消(外部提交的任務(wù))
                        if ((i & 1) == 0)
                            w.qlock = -1;     // try to disable external
                    }
                }
                // 循環(huán)數(shù)組兩次后,效驗(yàn)和都一致,代表任務(wù)都空了,
                // 同時(shí)也沒(méi)有新的線程被創(chuàng)建出來(lái),那么可以設(shè)置stop狀態(tài)了
                if (oldSum == (oldSum = checkSum))
                    break;
            }
        }
        // 如果線程池還未stop,那么則設(shè)置為stop狀態(tài)
        if ((runState & STOP) == 0) {
            rs = lockRunState();              // enter STOP phase
            unlockRunState(rs, (rs & ~RSLOCK) | STOP);
        }
    }
    
    int pass = 0;                             // 3 passes to help terminate
    for (long oldSum = 0L;;) {                // or until done or stable
        WorkQueue[] ws; WorkQueue w; ForkJoinWorkerThread wt; int m;
        long checkSum = ctl;
        // 所有隊(duì)列全部已經(jīng)空了或所有線程都注銷了
        if ((short)(checkSum >>> TC_SHIFT) + (config & SMASK) <= 0 ||
            (ws = workQueues) == null || (m = ws.length - 1) <= 0) {
            // 如果線程池是還不是TERMINATED狀態(tài)
            if ((runState & TERMINATED) == 0) {
                rs = lockRunState();          // done
                // 先將線程池狀態(tài)改為TERMINATED狀態(tài)
                unlockRunState(rs, (rs & ~RSLOCK) | TERMINATED);
                synchronized (this) { notifyAll(); } // for awaitTermination
            }
            break;
        }
        // 開啟循環(huán)
        for (int i = 0; i <= m; ++i) {
            // 處理每個(gè)隊(duì)列
            if ((w = ws[i]) != null) {
                checkSum += w.base;
                w.qlock = -1;                 // try to disable
                if (pass > 0) {
                    // 取消每個(gè)隊(duì)列中的所有任務(wù)
                    w.cancelAll();            // clear queue
                    // 中斷執(zhí)行線程,喚醒所有被掛起的線程
                    if (pass > 1 && (wt = w.owner) != null) {
                        if (!wt.isInterrupted()) {
                            try {             // unblock join
                                wt.interrupt();
                            } catch (Throwable ignore) {
                            }
                        }
                        if (w.scanState < 0)
                            U.unpark(wt);     // wake up
                    }
                }
            }
        }
        // 如果兩次效驗(yàn)和不一致,賦值上一次的效驗(yàn)和
        if (checkSum != oldSum) {             // unstable
            oldSum = checkSum;
            pass = 0;
        }
        // 線程池狀態(tài)穩(wěn)定了
        // 所有任務(wù)被取消,執(zhí)行線程被中斷,掛起線程被喚醒中斷了
        else if (pass > 3 && pass > m)        // can't further help
            break;
        // 如果有線程因?yàn)槭Щ畋粧炱?        else if (++pass > 1) {        // try to dequeue
            long c; int j = 0, sp;    // bound attempts
            // 根據(jù)ctl中記錄的阻塞鏈喚醒所有線程
            while (j++ <= m && (sp = (int)(c = ctl)) != 0)
                tryRelease(c, ws[sp & m], AC_UNIT);
        }
    }
    return true;
}

線程池關(guān)閉的實(shí)現(xiàn)邏輯也比較簡(jiǎn)單,首先會(huì)將線程池標(biāo)記為SHUTDOWN狀態(tài),然后根據(jù)情況進(jìn)行下一步處理,如果線程池中沒(méi)啥活躍線程了,同時(shí)任務(wù)也不多了,將狀態(tài)改為STOP狀態(tài),在STOP狀態(tài)中會(huì)處理四件事:

  • ①將所有活躍的隊(duì)列狀態(tài)改為注銷狀態(tài),w.qlock=-1
  • ②取消整個(gè)線程池中所有還未執(zhí)行的任務(wù)
  • ③喚醒所有因?yàn)槭Щ顠炱鹱枞木€程
  • ④嘗試中斷所有執(zhí)行的活躍線程,喚醒scanState<0的線程,確保一些還沒(méi)來(lái)得及掛起的線程也能被中斷

最后當(dāng)所有線程都被中斷了,并且未執(zhí)行的任務(wù)都被取消了,那么會(huì)把狀態(tài)改為TERMINATED狀態(tài),線程池關(guān)閉完成。

五、總結(jié)

ForkJoin分支合并框架幾乎是整個(gè)JUC包源碼中最難的部分,因?yàn)檎麄€(gè)框架比較龐大,分析起來(lái)也比較復(fù)雜,到目前為止還剩下ManagedBlockerCoutedCompleter沒(méi)有分析。因?yàn)閷?duì)于ForkJoin框架的分析篇幅比較長(zhǎng)了,所以對(duì)于這兩就不再進(jìn)行贅述,不過(guò)對(duì)CoutedCompleter比較感興趣的可以參考一下:《CoutedCompleter分析》這篇文章,它的作用更多的是為Java8的Stream并行流提供服務(wù)。而ManagedBlocker則是為ForkJoin框架提供處理阻塞型任務(wù)的支持。

總的來(lái)說(shuō),F(xiàn)orkJoin分支合并框架思想非常優(yōu)秀,完全的落地了分治以及工作竊取思想,整個(gè)框架中的各個(gè)成員各司其職卻有配合緊密,內(nèi)部采用了一個(gè)隊(duì)列數(shù)組以奇/偶位存儲(chǔ)內(nèi)外任務(wù),雙端隊(duì)列的方式實(shí)現(xiàn)工作與竊取思想。但是其內(nèi)部實(shí)現(xiàn)涉及了很多的位運(yùn)算知識(shí),所以半道出家以及工作多年的小伙伴會(huì)有些生疏,看其源碼實(shí)現(xiàn)會(huì)有些吃勁,但理解大體思想即可,對(duì)于任何源碼分析類的知識(shí)都無(wú)需拘泥其細(xì)節(jié)過(guò)程。

最后的總結(jié):

  • 創(chuàng)建池ForkJoinPool,初始化并行數(shù)=cpu邏輯核心數(shù),池中沒(méi)有隊(duì)列,沒(méi)有線程
  • 外部向線程池提交一個(gè)任務(wù):pool.submit(task)
  • 初始化隊(duì)列數(shù)組,容量:2 * Max { 并行數(shù), 2 ^ n }
  • 創(chuàng)建一個(gè)共享隊(duì)列,容量為2^13,隨機(jī)放在隊(duì)列數(shù)組的某一個(gè)偶數(shù)索引位
  • 外部提交的任務(wù)存入這個(gè)共享隊(duì)列,位置值為2^12處
  • 再創(chuàng)建一條線程,并為其分配一個(gè)隊(duì)列,容量為2^13,隨機(jī)放在數(shù)組中某個(gè)奇數(shù)索引位
  • 線程啟動(dòng)執(zhí)行
  • 隨機(jī)一個(gè)位置,線程從此位置開始遍歷所有隊(duì)列,最終掃描到前面提交的任務(wù),并將其從所在的隊(duì)列取出
  • 線程執(zhí)行處理任務(wù),首先拆分出兩個(gè)子任務(wù)
    • 如果用invokeAll提交,一個(gè)子任務(wù)執(zhí)行,另一個(gè)壓入隊(duì)列
    • 如果用fork提交,則兩個(gè)都?jí)喝牍ぷ麝?duì)列
  • 提交的子任務(wù)觸發(fā)創(chuàng)建新的線程并分配新的工作隊(duì)列,同樣放在奇數(shù)位置
  • 提交的子任務(wù)可能仍然被當(dāng)前線程執(zhí)行,但也有可能被其它線程竊取
  • 線程在子任務(wù)處join合并,join期間會(huì)幫助竊取者處理任務(wù),竊取它的任務(wù)執(zhí)行
    • 優(yōu)先偷竊取者隊(duì)列棧底的任務(wù)
    • 如果竊取者隊(duì)列為空,則會(huì)根據(jù)竊取鏈去找竊取者的竊取者偷任務(wù).....
    • 如果整個(gè)池內(nèi)都沒(méi)任務(wù)了,則進(jìn)入阻塞,阻塞前會(huì)根據(jù)情況補(bǔ)償活躍線程
  • 提交的子任務(wù)不管被哪條線程執(zhí)行,仍可能會(huì)重復(fù)上述拆分/提交/竊取/阻塞步驟
  • 當(dāng)任務(wù)被拆分的足夠細(xì),達(dá)到了拆分閾值時(shí),才會(huì)真正的開始執(zhí)行這些子任務(wù)
  • 處理完成會(huì)和拆分任務(wù)時(shí)一樣,遞歸一層一層返回結(jié)果
  • 直至最終所有子任務(wù)全部都執(zhí)行結(jié)束,從而合并所有子結(jié)果,得到最終結(jié)果
  • 如果外部沒(méi)有再提交任務(wù),所有線程掃描不到會(huì)被滅活,會(huì)進(jìn)入失活(inactive)狀態(tài)
  • 一直沒(méi)有任務(wù)時(shí),線程池會(huì)削減線程數(shù),直至最終所有線程銷毀,所有奇數(shù)索引位的隊(duì)列被注銷,F(xiàn)orkJoinPool中只剩下一個(gè)最初創(chuàng)建的在偶數(shù)索引位的隊(duì)列,以便于再次接受外部提交的任務(wù),然后再?gòu)念^開始重復(fù)所有步驟....
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,156評(píng)論 6 529
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,866評(píng)論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 174,880評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,398評(píng)論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,202評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,743評(píng)論 1 320
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,822評(píng)論 3 438
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 41,962評(píng)論 0 285
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,476評(píng)論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,444評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,579評(píng)論 1 365
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,129評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,840評(píng)論 3 344
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,231評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,487評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,177評(píng)論 3 388
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,568評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容