Java 線程池源碼詳解(2)-ForkJoinPool 源碼解析

前言

ForkJoinPool作爲線程池,ForkJoinTask爲任務,ForkJoinWorkerThread作爲執行任務的線程,三者構成的任務調度機制在 Java 中通常被稱爲ForkJoin 框架

ForkJoin框架在 Java 7 的時候就加入到了 Java 併發包 java.util.concurrent,並且在 Java 8 的 lambda 並行流 中充當着底層框架的角色。其主要實現的功能是採用“分而治之”的算法將一個大型複雜任務 fork()分解成足夠小的任務才使用多線程去並行處理這些小任務,處理完得到各個小任務的執行結果後再進行join()合併,將其彙集成一個大任務的結果,最終得到最初提交的那個大型複雜任務的執行結果

ForkJoin框架適用於非循環依賴的純函數的計算或孤立對象的操作,如果存在 I/O,線程間同步,sleep() 等會造成線程長時間阻塞的情況時會因爲任務依賴影響整體效率,此時可配合使用 ManagedBlockerForkJoinPool 線程池爲了提高任務的並行度和吞吐量做了很多複雜的設計實現,目的是充分利用CPU,其中最著名的就是 work stealing任務竊取機制

  • ManagedBlocker 相當於明確告訴 ForkJoinPool 有個任務要阻塞了,ForkJoinPool 就會啓用另一個線程來運行任務,以最大化地利用CPU

在這裏插入圖片描述

1. ForkJoinPool 的組件

  • ForkJoin框架各組件關係如下所示。ForkJoinPool 作爲最核心的組件,維護了所有任務隊列的數組workQueues。這個數組的結構與 HashMap 底層數組一致,大小爲 2 的次冪,每個工作線程用 ThreadLocalRandom.getProbe()作爲 hash 值,經過計算即可得出工作線程私有的任務隊列WorkQueue在任務隊列數組workQueues中的數組下標

    另外需注意,WorkQueue 任務隊列其實也分爲了兩種類型,一種是 Submission Queue,也就是外部提交進來的任務所佔用的隊列,其在任務隊列數組中的數組下標爲偶數;另一種是Work Queue,屬於工作線程私有的任務隊列,保存大任務 fork 分解出來的任務,其在任務隊列數組中的數組下標爲奇數

  • Work Queue類型的WorkQueue對象通過變量 pool 持有 ForkJoinPool線程池 引用,用來獲取其 workQueues 任務隊列數組,從而竊取其他工作線程的任務來執行。WorkQueue中的ForkJoinTask<?>[] array數組保存了每一個具體的任務,數組的大小也爲 2 的次冪,這個數據是爲了高效定位數組中的元素。其初始化大小爲 8192,任務在數組中的下標初始化爲 4096,插入數組中的第一個任務通常是處理量最大的任務WorkQueue對象中的owner指向其所屬的ForkJoinWorkerThread工作線程,這樣ForkJoinPool 就可以通過 WorkQueue 來操作工作線程

  • ForkJoinWorkerThread工作線程啓動後就會掃描偷取任務執行,另外當其在 ForkJoinTask#join() 等待返回結果時如果被 ForkJoinPool 線程池發現其任務隊列爲空或者已經將當前任務執行完畢,也會通過工作竊取算法從其他任務隊列中獲取任務分配到其任務隊列中並執行

1.1 線程池 ForkJoinPool

ForkJoinPool 的繼承體系如下,可以看到它和 ThreadPoolExecutor 一樣都是繼承自AbstractExecutorService抽象類,所以它和 ThreadPoolExecutor 的使用幾乎沒什麼區別,只是任務對象變成了 ForkJoinTask

在這裏插入圖片描述

1.1.1 ForkJoinPool 線程池的創建

與其他線程池類似,ForkJoinPool 線程池可使用以下方式創建

  • Executors.newWorkStealingPool()
    public static ExecutorService newWorkStealingPool(int parallelism) {
         return new ForkJoinPool
             (parallelism,
              ForkJoinPool.defaultForkJoinWorkerThreadFactory,
              null, true);
     }
    

構造方法中各個參數的含義如下:

  1. parallelism: 並行度
    即配置線程池線程個數,如果沒有指定,則默認爲Runtime.getRuntime().availableProcessors() - 1,最大值不能超過 MAX_CAP =32767,可通過以下方式自定義配置
    System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "8");
    // 或者啓動參數指定
    -Djava.util.concurrent.ForkJoinPool.common.parallelism=8
    
  2. factory: 工作線程 ForkJoinWorkerThread 的創建工廠
  3. handler: 未捕獲異常的處理器類
    多線程中子線程拋出的異常是無法被主線程 catch 的,因此可能需要使用 UncaughtExceptionHandler 對異常進行統一處理
  4. asyncMode: 任務隊列處理任務模式
    默認爲 false,使用 LIFO(後入先出,類似棧結構)策略處理任務;爲 true 時處理策略是 FIFO(先進先出),更接近於一個消息隊列,不適用於處理遞歸式的任務

1.1.2 ForkJoinPool 線程池內部重要屬性

    // 配合ctl在控制線程數量時使用
    private static final long ADD_WORKER = 0x0001L << (TC_SHIFT + 15); 
  
    // 控制ForkJoinPool創建線程數量,(ctl & ADD_WORKER) != 0L 時創建線程,也就是當ctl的第16位不爲0時,可以繼續創建線程
    volatile long ctl;                   // main pool control
    // 全局鎖控制,全局運行狀態
    volatile int runState;               // lockable status
    // 低16位表示並行數量,高16位表示 ForkJoinPool 處理任務的模式(異步/同步)
    final int config;                    // parallelism, mode
    // 工作任務隊列數組
    volatile WorkQueue[] workQueues;     // main registry
    // 默認線程工廠,默認實現是DefaultForkJoinWorkerThreadFactory
    final ForkJoinWorkerThreadFactory factory;

1.2 工作線程 ForkJoinWorkerThread

ForkJoinWorkerThread的繼承體系如下, 它繼承了 Thread 類,主要在 Thread的基礎上新增了變量,並重寫run() 方法

在這裏插入圖片描述

1.2.1 ForkJoinWorkerThread 的創建

與其他線程池類似,ForkJoinWorkerThread 線程由線程池創建,創建後立即調用start()方法啓動。 ForkJoinWorkerThread創建時會向ForkJoinPool線程池註冊,此時線程池會將其設置爲守護線程,併爲其創建 ForkJoinPool.WorkQueue 隊列後將引用返回,之後ForkJoinWorkerThread就可以從 workQueue 裏面取任務出來處理了

  • ForkJoinPool#createWorker()

    private boolean createWorker() {
         ForkJoinWorkerThreadFactory fac = factory;
         Throwable ex = null;
         ForkJoinWorkerThread wt = null;
         try {
             if (fac != null && (wt = fac.newThread(this)) != null) {
                 wt.start();
                 return true;
             }
         } catch (Throwable rex) {
             ex = rex;
         }
         deregisterWorker(wt, ex);
         return false;
     }
    
  • ForkJoinWorkerThread#ForkJoinWorkerThread()

    protected ForkJoinWorkerThread(ForkJoinPool pool) {
         // Use a placeholder until a useful name can be set in registerWorker
         super("aForkJoinWorkerThread");
         this.pool = pool;
         this.workQueue = pool.registerWorker(this);
     }
    

1.2.2 ForkJoinWorkerThread 重要屬性

   // 工作線程所屬線程池
   final ForkJoinPool pool;                // the pool this thread works in
   // 私有的任務隊列,在 ForkJoinWorkerThread 新建的時候由線程池爲其創建
   final ForkJoinPool.WorkQueue workQueue; // work-stealing mechanics

1.3 線程任務 ForkJoinTask

ForkJoinTaskFutureTask一樣實現了Future接口,不過它是一個抽象類,使用時通常不會直接實現ForkJoinTask,而是實現其三個抽象子類。ForkJoinTask僅僅用於配合ForkJoinPool實現任務的調度執行,實現其抽象子類則側重於提供任務的拆分與執行邏輯

  • RecursiveAction
    用於大多數不返回結果的計算
  • RecursiveTask
    用於返回結果的計算
  • CountedCompleter
    用於那些操作完成之後觸發其他操作的操作,提供了外部回調方法, Java 8 新增,在 Stream 並行流中使用極多。CountedCompleter 在創建實例的時候還可以傳入一個CountedCompleter實例,因此可以形成樹狀的任務結構,樹上的所有任務是可以並行執行的,且每一個子任務完成後都可以通過tryComplete()輔助其父任務的完成

在這裏插入圖片描述

1.3.1 ForkJoinTask 重要屬性

status 字段將運行控制狀態位打包到單個 int 中,以最小化佔用空間並通過CAS確保原子性,其高16位存儲任務執行狀態例如 NORMAL,CANCELLED 或 EXCEPTIONAL,低16位預留用於用戶自定義的標記

  • 任務未完成之前status大於等於0,完成之後就是 NORMAL、CANCELLED 或 EXCEPTIONAL這幾個小於 0 的值,這幾個值也有大小順序:0(初始狀態) > NORMAL > CANCELLED > EXCEPTIONAL
    /*
     * The status field holds run control status bits packed into a
     * single int to minimize footprint and to ensure atomicity (via
     * CAS).  Status is initially zero, and takes on nonnegative
     * values until completed, upon which status (anded with
     * DONE_MASK) holds value NORMAL, CANCELLED, or EXCEPTIONAL. Tasks
     * undergoing blocking waits by other threads have the SIGNAL bit
     * set.  Completion of a stolen task with SIGNAL set awakens any
     * waiters via notifyAll. Even though suboptimal for some
     * purposes, we use basic builtin wait/notify to take advantage of
     * "monitor inflation" in JVMs that we would otherwise need to
     * emulate to avoid adding further per-task bookkeeping overhead.
     * We want these monitors to be "fat", i.e., not use biasing or
     * thin-lock techniques, so use some odd coding idioms that tend
     * to avoid them, mainly by arranging that every synchronized
     * block performs a wait, notifyAll or both.
     *
     * These control bits occupy only (some of) the upper half (16
     * bits) of status field. The lower bits are used for user-defined
     * tags.
     */

    /** The run status of this task */
    volatile int status; // accessed directly by pool and workers
    static final int DONE_MASK   = 0xf0000000;  // mask out non-completion bits
    static final int NORMAL      = 0xf0000000;  // must be negative
    static final int CANCELLED   = 0xc0000000;  // must be < NORMAL
    static final int EXCEPTIONAL = 0x80000000;  // must be < CANCELLED
    static final int SIGNAL      = 0x00010000;  // must be >= 1 << 16
    static final int SMASK       = 0x0000ffff;  // short bits for tags

2. 任務處理流程

在這裏插入圖片描述

2.1 任務的提交

2.1.1 通過 ForkJoinPool 的外部提交

2.1.1.1 外部任務提交的方法

ForkJoinPool 主要提供了以下三個方法來完成任務的提交。這三個方法在處理流程上並無明顯差別,主要差異體現在其方法返回值上

方法 特性
invoke() 返回任務執行的結果
execute() 無返回值
submit() 返回 ForkJoinTask 對象,可以通過該對象取消任務/獲取結果
2.1.1.2 外部任務提交的流程
  1. ForkJoinPool#invoke() 方法爲觸發起點,可以看到其首先調用 externalPush(task) 方法將任務提交, 然後調用task.join()等待獲取任務執行結果

    public <T> T invoke(ForkJoinTask<T> task) {
         if (task == null)
             throw new NullPointerException();
         externalPush(task);
         return task.join();
     }
    
  2. ForkJoinPool#externalPush() 首先需要判斷線程池中的任務隊列數組 workQueues 以及當前線程所對應的Submission Queue是否已經創建,如果已經創建了,則將任務入隊;如果沒有創建,則調用 externalSubmit(task)

    final void externalPush(ForkJoinTask<?> task) {
         WorkQueue[] ws; WorkQueue q; int m;
         int r = ThreadLocalRandom.getProbe();
         int rs = runState;
         if ((ws = workQueues) != null && (m = (ws.length - 1)) >= 0 &&
             (q = ws[m & r & SQMASK]) != null && r != 0 && rs > 0 &&
             U.compareAndSwapInt(q, QLOCK, 0, 1)) {
             ForkJoinTask<?>[] a; int am, n, s;
             if ((a = q.array) != null &&
                 (am = a.length - 1) > (n = (s = q.top) - q.base)) {
                 int j = ((am & s) << ASHIFT) + ABASE;
                 U.putOrderedObject(a, j, task);
                 U.putOrderedInt(q, QTOP, s + 1);
                 U.putIntVolatile(q, QLOCK, 0);
                 if (n <= 1)
                     signalWork(ws, q);
                 return;
             }
             U.compareAndSwapInt(q, QLOCK, 1, 0);
         }
         externalSubmit(task);
     }
    
  3. ForkJoinPool#externalSubmit() 內部是一個空循環,跳出邏輯主要結合runState來控制,內部工作分爲3步,每個步驟都使用了鎖的機制來處理併發事件,既有對 runState 使用 ForkJoinPool 的全局鎖,也有對WorkQueue使用局部鎖

    1. 完成任務隊列數組 workQueues的初始化,數組長度爲大於等於2倍並行數量的且是2的n次冪的數,然後扭轉 runState
    2. 計算出一個workQueues數組偶數下標,如該位置沒有任務隊列,新建一個 Submission Queue 隊列,並將其添加到 workQueues 數組對應下標。隊列中的ForkJoinTask<?>[] array數組初始化大小爲 8192,雙端指針base/top初始化爲 4096(top指針用來表示任務正常出隊入隊,每當有任務入隊 top +1,每當有任務出列 top -1。base指針用來表示任務偷取狀況,每當任務被偷取 base +1。當 base 小於top時,說明有數據可以取)
    3. 計算出的workQueues數組偶數下標位置已經有任務隊列,將任務 CAS 入隊,任務隊列 top +1,標記任務爲已提交,並調用 signalWork(),跳出空循環
    private void externalSubmit(ForkJoinTask<?> task) {
        int r;                                    // initialize caller's probe
        if ((r = ThreadLocalRandom.getProbe()) == 0) {
            ThreadLocalRandom.localInit();
            r = ThreadLocalRandom.getProbe();
        }
        for (;;) {
            WorkQueue[] ws; WorkQueue q; int rs, m, k;
            boolean move = false;
            if ((rs = runState) < 0) {
                tryTerminate(false, false);     // help terminate
                throw new RejectedExecutionException();
            }
            else if ((rs & STARTED) == 0 ||     // initialize
                     ((ws = workQueues) == null || (m = ws.length - 1) < 0)) {
                int ns = 0;
                rs = lockRunState();
                try {
                    if ((rs & STARTED) == 0) {
                        U.compareAndSwapObject(this, STEALCOUNTER, null,
                                               new AtomicLong());
                        // create workQueues array with size a power of two
                        int p = config & SMASK; // ensure at least 2 slots
                        int n = (p > 1) ? p - 1 : 1;
                        n |= n >>> 1; n |= n >>> 2;  n |= n >>> 4;
                        n |= n >>> 8; n |= n >>> 16; n = (n + 1) << 1;
                        workQueues = new WorkQueue[n];
                        ns = STARTED;
                    }
                } finally {
                    unlockRunState(rs, (rs & ~RSLOCK) | ns);
                }
            }
            else if ((q = ws[k = r & m & SQMASK]) != null) {
                if (q.qlock == 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) {
                    ForkJoinTask<?>[] a = q.array;
                    int s = q.top;
                    boolean submitted = false; // initial submission or resizing
                    try {                      // locked version of push
                        if ((a != null && a.length > s + 1 - q.base) ||
                            (a = q.growArray()) != null) {
                            int j = (((a.length - 1) & s) << ASHIFT) + ABASE;
                            U.putOrderedObject(a, j, task);
                            U.putOrderedInt(q, QTOP, s + 1);
                            submitted = true;
                        }
                    } finally {
                        U.compareAndSwapInt(q, QLOCK, 1, 0);
                    }
                    if (submitted) {
                        signalWork(ws, q);
                        return;
                    }
                }
                move = true;                   // move on failure
            }
            else if (((rs = runState) & RSLOCK) == 0) { // create new queue
                q = new WorkQueue(this, null);
                q.hint = r;
                q.config = k | SHARED_QUEUE;
                q.scanState = INACTIVE;
                rs = lockRunState();           // publish index
                if (rs > 0 &&  (ws = workQueues) != null &&
                    k < ws.length && ws[k] == null)
                    ws[k] = q;                 // else terminated
                unlockRunState(rs, rs & ~RSLOCK);
            }
            else
                move = true;                   // move if busy
            if (move)
                r = ThreadLocalRandom.advanceProbe(r);
        }
    }
    
  4. ForkJoinPool#signalWork() 會根據ctl的值判斷是否需要創建工作線程,當前工作線程太少會調用tryAddWorker()嘗試創建新的工作線程;其他情況如果有工作線程在休眠,會U.unpark(p)將其喚醒

    final void signalWork(WorkQueue[] ws, WorkQueue q) {
       long c; int sp, i; WorkQueue v; Thread p;
       while ((c = ctl) < 0L) {                       // too few active
           if ((sp = (int)c) == 0) {                  // no idle workers
               if ((c & ADD_WORKER) != 0L)            // too few workers
                   tryAddWorker(c);
               break;
           }
           if (ws == null)                            // unstarted/terminated
               break;
           if (ws.length <= (i = sp & SMASK))         // terminated
               break;
           if ((v = ws[i]) == null)                   // terminating
               break;
           int vs = (sp + SS_SEQ) & ~INACTIVE;        // next scanState
           int d = sp - v.scanState;                  // screen CAS
           long nc = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & v.stackPred);
           if (d == 0 && U.compareAndSwapLong(this, CTL, c, nc)) {
               v.scanState = vs;                      // activate v
               if ((p = v.parker) != null)
                   U.unpark(p);
               break;
           }
           if (q != null && q.base == q.top)          // no more work
               break;
       }
    }
    
  5. ForkJoinPool#tryAddWorker() 會一直嘗試調用createWorker() 創建工作線程,直到控制工作線程數量的相關標記達到目標值

    private void tryAddWorker(long c) {
       boolean add = false;
       do {
           long nc = ((AC_MASK & (c + AC_UNIT)) |
                      (TC_MASK & (c + TC_UNIT)));
           if (ctl == c) {
               int rs, stop;                 // check if terminating
               if ((stop = (rs = lockRunState()) & STOP) == 0)
                   add = U.compareAndSwapLong(this, CTL, c, nc);
               unlockRunState(rs, rs & ~RSLOCK);
               if (stop != 0)
                   break;
               if (add) {
                   createWorker();
                   break;
               }
           }
       } while (((c = ctl) & ADD_WORKER) != 0L && (int)c == 0);
    }
    
  6. ForkJoinPool#createWorker() 主要負責通過線程工廠創建工作線程,並調用其start() 方法啓動線程

    private boolean createWorker() {
        ForkJoinWorkerThreadFactory fac = factory;
        Throwable ex = null;
        ForkJoinWorkerThread wt = null;
        try {
            if (fac != null && (wt = fac.newThread(this)) != null) {
                wt.start();
                return true;
            }
        } catch (Throwable rex) {
            ex = rex;
        }
        deregisterWorker(wt, ex);
        return false;
    }
    
  7. 線程工廠最終調用到ForkJoinWorkerThread#ForkJoinWorkerThread()來創建一個工作線程,並將其向ForkJoinPool線程池註冊,之後線程池會爲其創建私有的 Work Queue 任務隊列後將引用返回

    protected ForkJoinWorkerThread(ForkJoinPool pool) {
         // Use a placeholder until a useful name can be set in registerWorker
         super("aForkJoinWorkerThread");
         this.pool = pool;
         this.workQueue = pool.registerWorker(this);
     }
    
  8. ForkJoinPool#registerWorker() 將工作線程設置爲守護線程,並將新建的Work Queue 任務隊列放入任務隊列數組奇數下標中。至此任務隊列數組workQueus中已經有了兩種任務隊列Submission QueueWork Queue

    final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
         UncaughtExceptionHandler handler;
         wt.setDaemon(true);                           // configure thread
         if ((handler = ueh) != null)
             wt.setUncaughtExceptionHandler(handler);
         WorkQueue w = new WorkQueue(this, wt);
         int i = 0;                                    // assign a pool index
         int mode = config & MODE_MASK;
         int rs = lockRunState();
         try {
             WorkQueue[] ws; int n;                    // skip if no array
             if ((ws = workQueues) != null && (n = ws.length) > 0) {
                 int s = indexSeed += SEED_INCREMENT;  // unlikely to collide
                 int m = n - 1;
                 i = ((s << 1) | 1) & m;               // odd-numbered indices
                 if (ws[i] != null) {                  // collision
                     int probes = 0;                   // step by approx half n
                     int step = (n <= 4) ? 2 : ((n >>> 1) & EVENMASK) + 2;
                     while (ws[i = (i + step) & m] != null) {
                         if (++probes >= n) {
                             workQueues = ws = Arrays.copyOf(ws, n <<= 1);
                             m = n - 1;
                             probes = 0;
                         }
                     }
                 }
                 w.hint = s;                           // use as random seed
                 w.config = i | mode;
                 w.scanState = i;                      // publication fence
                 ws[i] = w;
             }
         } finally {
             unlockRunState(rs, rs & ~RSLOCK);
         }
         wt.setName(workerNamePrefix.concat(Integer.toString(i >>> 1)));
         return w;
     }
    

2.1.2 通過 ForkJoinTask 的內部提交

ForkJoinTask#fork()提交,返回 ForkJoinTask對象,通過該對象可以對任務進行控制,比如獲取、取消等操作。該方法首先判斷當前線程是否是 ForkJoinWorkerThread 線程,如果是說明是內部大任務分解的小任務,直接將任務插入其私有隊列,如果不是說明是外部提交,需要走外部任務提交流程

  • 入隊時將新任務始終 push 到隊列一端的方式可以保證較大的任務在隊列的頭部,越小的任務越在尾部。這時候擁有該任務隊列的工作線程如果按照LIFO(棧結構) 的方式彈出任務執行,將會優先從小任務開始,逐漸往大任務進行,而竊取任務的其他工作線程從隊列頭部(FIFO方式)開始竊取將會幫助它完成大任務
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;
    }

2.2 任務的處理

2.2.1 ForkJoinWorkerThread 的任務處理循環

  1. ForkJoinWorkerThread工作線程啓動後,其 run()方法就會被調用。方法中首先是判斷當前工作線程的 workQueue.array是否爲 null,毫無疑問,這個判斷只在工作線程創建的時候成立一次,之後就會調用線程池的 runWorker()方法

    public void run() {
         if (workQueue.array == null) { // only run once
             Throwable exception = null;
             try {
                 onStart();
                 pool.runWorker(workQueue);
             } catch (Throwable ex) {
                 exception = ex;
             } finally {
                 try {
                     onTermination(exception);
                 } catch (Throwable ex) {
                     if (exception == null)
                         exception = ex;
                 } finally {
                     pool.deregisterWorker(this, exception);
                 }
             }
         }
     }
    
  2. ForkJoinPool#runWorker() 開始初始化 WorkQueue 工作隊列中的 array 數組,並開啓了空循環一直調用 scan() 方法掃描偷取任務,獲取到任務後即執行

    final void runWorker(WorkQueue w) {
        // 1. 初始化或者兩倍擴容 array 數組
         w.growArray();                   // allocate queue
         int seed = w.hint;               // initially holds randomization hint
         int r = (seed == 0) ? 1 : seed;  // avoid 0 for xorShift
         // 2. 空循環
         for (ForkJoinTask<?> t;;) {
         // 3. scan()方法掃描偷取任務,working stealing 算法
             if ((t = scan(w, r)) != null)
             // 4. 偷取到任務即執行
                 w.runTask(t);
              // 5. 未偷取到任務則 await 等待
             else if (!awaitWork(w, r))
                 break;
             r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
         }
     }
    
  3. ForkJoinPool#scan()work stealing 算法的重要實現,此處暫不分析,接下去看偷取到任務後的執行邏輯 WorkQueue#runTask(),該方法的主要邏輯先調用 (currentSteal = task).doExec() 方法執行偷取到的任務,再調用 execLocalTasks() 方法執行當前任務隊列中的任務

    final void runTask(ForkJoinTask<?> task) {
             if (task != null) {
                 scanState &= ~SCANNING; // mark as busy
                 (currentSteal = task).doExec();
                 U.putOrderedObject(this, QCURRENTSTEAL, null); // release for GC
                 execLocalTasks();
                 ForkJoinWorkerThread thread = owner;
                 if (++nsteals < 0)      // collect on overflow
                     transferStealCount(pool);
                 scanState |= SCANNING;
                 if (thread != null)
                     thread.afterTopLevelExec();
             }
         }
    
  4. ForkJoinTask#doExec() 方法首先判斷任務狀態是否大於等於 0 (大於等於 0 說明任務還沒有執行完成),因爲這個任務可能被其它線程竊取過去處理完了。方法內主要是執行 exec() 方法,再根據方法返回值設置任務的完成狀態

    final int doExec() {
         int s; boolean completed;
         if ((s = status) >= 0) {
             try {
                 completed = exec();
             } catch (Throwable rex) {
                 return setExceptionalCompletion(rex);
             }
             if (completed)
                 s = setCompletion(NORMAL);
         }
         return s;
     }
    
  5. ForkJoinTask#exec() 是抽象方法,主要由子類重寫。以CountedCompleter#exec()方法爲例,其內部其實就是調用 compute() 方法,而這個方法正是 CountedCompleter子類重寫實現,內部主要爲任務分解與計算的邏輯。任務分解會調用 ForkJoinTask#fork() 方法,根據當前線程實例決定將其提交到線程池Submission Queue 任務隊列還是工作線程 Work Queue 私有任務隊列

    protected final boolean exec() {
        compute();
        return false;
    }
    

2.2.2 join() 觸發的處理流程

ForkJoinTask#join() 會等待任務執行完成,並返回執行結果。若任務被取消拋出CancellationException異常,若是其他異常導致異常結束則拋出相關RuntimeExceptionError信息,這些異常可能包括由於內部資源耗盡而導致的RejectedExecutionException,比如分配內部任務隊列失敗

  1. ForkJoinTask#join() 方法的內部邏輯很簡單,就是調用 doJoin() 方法,然後根據任務的狀態決定是否要reportException()報告異常,如果任務正常結束,就調用 getRawResult()返回執行結果

    public final V join() {
         int s;
         if ((s = doJoin() & DONE_MASK) != NORMAL)
             reportException(s);
         return getRawResult();
     }
    
  2. ForkJoinTask#doJoin() 方法內部邏輯很複雜,主要分爲了以下幾步:

    1. doJoin() 首先判斷staus是不是已完成,如果完成了(status < 0)就直接返回,因爲這個任務可能被其它線程竊取過去處理掉了
    2. 其次判斷當前線程是否是 ForkJoinWorkerThread:
      • 是的話直接嘗試 tryUnpush(this) 出隊然後doExec()執行任務處理。如果沒有出隊成功並且處理成功,則執行wt.pool.awaitJoin(w, this, 0L),等待任務執行完成
      • 不是的話執行 externalAwaitDone() 等待外部任務執行完成
    private int doJoin() {
         int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
         return (s = status) < 0 ? s :
             ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
             (w = (wt = (ForkJoinWorkerThread)t).workQueue).
             tryUnpush(this) && (s = doExec()) < 0 ? s : //調用pool的執行邏輯,並等待返回執行結果狀態
             wt.pool.awaitJoin(w, this, 0L) : //調用pool的等待機制
             externalAwaitDone();
     }
    
  3. wt.pool.awaitJoin(w, this, 0L)執行了線程池的方法 ForkJoinPool#awaitJoin(),其內部邏輯如下

    1. 更新 WorkQueue 的 currentJoin
    2. 空循環開啓,如果任務已經結束((s = task.status) < 0),則 break
    3. 如果任務爲 CountedCompleter 類型,則調用 helpComplete() 幫助父任務完成
    4. 隊列爲空或者任務已經執行成功(可能被其他線程偷取),則幫助偷取了自己任務的工作線程執行任務(互相幫助)helpStealer()
    5. 如果任務已經結束((s = task.status) < 0),則 break
    6. 如果超時結束,則 break
    7. 執行失敗的情況下,執行補償操作 tryCompensate()
    8. 當前任務完成後,替換 currentJoin 爲以前的值
    final int awaitJoin(WorkQueue w, ForkJoinTask<?> task, long deadline) {
         int s = 0;
         if (task != null && w != null) {
             ForkJoinTask<?> prevJoin = w.currentJoin;
             U.putOrderedObject(w, QCURRENTJOIN, task);
             CountedCompleter<?> cc = (task instanceof CountedCompleter) ?
                 (CountedCompleter<?>)task : null;
             for (;;) {
                 if ((s = task.status) < 0)
                     break;
                 if (cc != null)
                     helpComplete(w, cc, 0);
                 else if (w.base == w.top || w.tryRemoveAndExec(task))
                     helpStealer(w, task);
                 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;
                 if (tryCompensate(w)) {
                     task.internalWait(ms);
                     U.getAndAddLong(this, CTL, AC_UNIT);
                 }
             }
             U.putOrderedObject(w, QCURRENTJOIN, prevJoin);
         }
         return s;
     }
    
  4. ForkJoinTask#externalAwaitDone() 的處理邏輯也比較簡單,步驟如下:

    1.首先判斷當前任務是否是 CountedCompleter,是的話調用線程池 externalHelpComplete() 嘗試將其入Submission Queue 隊列,由某個工作線程偷取幫助執行;否則調用ForkJoinPool.common.tryExternalUnpush(this) ? doExec()嘗試把自己出隊然後執行掉
    2. 如果任務沒有執行成功,利用 object/wait 的方法去監聽任務 status 的狀態變更

    private int externalAwaitDone() {
         int s = ((this instanceof CountedCompleter) ? // try helping
                  ForkJoinPool.common.externalHelpComplete(
                      (CountedCompleter<?>)this, 0) :
                  ForkJoinPool.common.tryExternalUnpush(this) ? doExec() : 0);
         if (s >= 0 && (s = status) >= 0) {
             boolean interrupted = false;
             do {
                 if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) {
                     synchronized (this) {
                         if (status >= 0) {
                             try {
                                 wait(0L);
                             } catch (InterruptedException ie) {
                                 interrupted = true;
                             }
                         }
                         else
                             notifyAll();
                     }
                 }
             } while ((s = status) >= 0);
             if (interrupted)
                 Thread.currentThread().interrupt();
         }
         return s;
     }
    

3. work stealing 算法

在這裏插入圖片描述

work stealing 算法 的實現主要體現在 ForkJoinPool#scan() 方法,其主要機制如下:

  1. ForkJoinPool 的每個工作線程都維護着一個私有的雙端任務隊列 Work Queue,每個工作線程在運行中產生的新任務(通常是因爲調用了 fork()),會放入工作隊列的隊尾,並且工作線程在處理自己的工作隊列時,使用的是 LIFO 方式,其實就是棧結構
  2. 每個工作線程在處理自己的工作隊列同時,會嘗試竊取一個任務(剛剛提交到線程池的 Submission Queue 隊列中的任務,或是來自於其他工作線程的工作隊列),竊取的任務位於其他線程的工作隊列的隊首,也就是使用的 FIFO 方式,目的是減少競爭
  3. 在遇到 join() 時,如果需要 join 的任務尚未完成,則會先處理其他任務,並等待其完成
  4. 在既沒有自己的任務,也沒有可以竊取的任務時,進入休眠

ForkJoinPool除了工作竊取機制,還有一種互助機制,體現在ForkJoinPool#helpStealer()方法 。假設工作線程2竊取了工作線程1的任務之後,通過 fork又分解產生了子任務,這些子任務會進入工作線程2的工作隊列。這時候如果工作線程1把剩餘的任務都完成了,當它發現自己的任務被工作線程2竊取了,那它也會試着去竊取工作線程2的任務 (你偷了我的,我有空就要偷你的),這就是互助機制

private ForkJoinTask<?> scan(WorkQueue w, int r) {
        WorkQueue[] ws; int m;
        if ((ws = workQueues) != null && (m = ws.length - 1) > 0 && w != null) {
            int ss = w.scanState;                     // initially non-negative
            for (int origin = r & m, k = origin, oldSum = 0, checkSum = 0;;) {
                WorkQueue q; ForkJoinTask<?>[] a; ForkJoinTask<?> t;
                int b, n; long c;
                if ((q = ws[k]) != null) {
                    if ((n = (b = q.base) - q.top) < 0 &&
                        (a = q.array) != null) {      // non-empty
                        long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
                        if ((t = ((ForkJoinTask<?>)
                                  U.getObjectVolatile(a, i))) != null &&
                            q.base == b) {
                            if (ss >= 0) {
                                if (U.compareAndSwapObject(a, i, t, null)) {
                                    q.base = b + 1;
                                    if (n < -1)       // signal others
                                        signalWork(ws, q);
                                    return t;
                                }
                            }
                            else if (oldSum == 0 &&   // try to activate
                                     w.scanState < 0)
                                tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
                        }
                        if (ss < 0)                   // refresh
                            ss = w.scanState;
                        r ^= r << 1; r ^= r >>> 3; r ^= r << 10;
                        origin = k = r & m;           // move and rescan
                        oldSum = checkSum = 0;
                        continue;
                    }
                    checkSum += b;
                }
                if ((k = (k + 1) & m) == origin) {    // continue until stable
                    if ((ss >= 0 || (ss == (ss = w.scanState))) &&
                        oldSum == (oldSum = checkSum)) {
                        if (ss < 0 || w.qlock < 0)    // already inactive
                            break;
                        int ns = ss | INACTIVE;       // try to inactivate
                        long nc = ((SP_MASK & ns) |
                                   (UC_MASK & ((c = ctl) - AC_UNIT)));
                        w.stackPred = (int)c;         // hold prev stack top
                        U.putInt(w, QSCANSTATE, ns);
                        if (U.compareAndSwapLong(this, CTL, c, nc))
                            ss = ns;
                        else
                            w.scanState = ss;         // back out
                    }
                    checkSum = 0;
                }
            }
        }
        return null;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章