ForkJoinPool實現原理和源碼解析

這是在部門做技術分享的時候寫的一篇學習筆記,順便貼在這裏給大家看看,歡迎指出錯誤,共同學習

ForkJoin是什麼

ForkJoin是用於並行執行任務的框架, 是一個把大任務分割成若干個小任務,最終彙總每個小任務結果後得到大任務結果的框架。Fork就是把一個大任務切分爲若干子任務並行的執行,Join就是合併這些子任務的執行結果,最後得到這個大任務的結果。
在這裏插入圖片描述

數據結構

在這裏插入圖片描述

關鍵調用圖

在這裏插入圖片描述

源碼解析

1、pool屬性

    // Instance fields
    volatile long ctl;                   // 控制中心:非常重要,看下圖解析
    volatile int runState;               // 負數是shutdown,其餘都是2的次方
    final int config;                    // 配置:二進制的低16位代表 並行度(parallelism),
																					//高16位:mode可選FIFO_QUEUE(1 << 16)和LIFO_QUEUE(1 << 31),默認是LIFO_QUEUE
    int indexSeed;                       // 生成worker的queue索引
    volatile WorkQueue[] workQueues;     // main registry
    final ForkJoinWorkerThreadFactory factory;
    final UncaughtExceptionHandler ueh;  // per-worker UEH
    final String workerNamePrefix;       // to create worker name string
    volatile AtomicLong stealCounter;    // also used as sync monitor

2、控制中心:ctl

在這裏插入圖片描述

3、WorkQueue

        // Instance fields
        volatile int scanState;    // 負數:inactive, 非負數:active, 其中奇數代表scanning
        int stackPred;             // sp = (int)ctl, 前一個隊列棧的標示信息,包含版本號、是否激活、以及隊列索引
        int nsteals;               // 竊取的任務數
        int hint;                  // 一個隨機數,用來幫助任務竊取,在 helpXXXX()的方法中會用到
        int config;                // 配置:二進制的低16位代表 在 queue[] 中的索引,
																	 // 高16位:mode可選FIFO_QUEUE(1 << 16)和LIFO_QUEUE(1 << 31),默認是LIFO_QUEUE
        volatile int qlock;        // 鎖定標示位:1: locked, < 0: terminate; else 0
        volatile int base;         // index of next slot for poll
        int top;                   // index of next slot for push
        ForkJoinTask<?>[] array;   // 任務列表

4、externalPush || externalSubmit

    final void externalPush(ForkJoinTask<?> task) {
        WorkQueue[] ws; WorkQueue q; int m;
        //我們以前常用的Random,在併發下,多個線程同時計算種子需要用到同一個原子變量。
        //由於更新操作使用CAS,同時執行只有一個線程成功,其他線程的大量自旋造成性能損失,ThreadLocalRandom繼承Random,對此進行了改進。
				//ThreadLocalRandom運用了ThreadLocal,每個線程內部維護一個種子變量,多線程下計算新種子時使用線程自己的種子變量進行更新,避免了競爭。
        int r = ThreadLocalRandom.getProbe();
        int rs = runState;
        // 外部提交的task,肯定會到偶數位下標的隊列上
        // SQMASK = 0x007e = 1111110,任何數和 SQMASK 進行 & 運算 都會是偶數
        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;
                //把 task 放到隊列的 top端
                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);
    }

5、righsterWorker

    final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
        //......
            if ((ws = workQueues) != null && (n = ws.length) > 0) {
                int s = indexSeed += SEED_INCREMENT;  // unlikely to collide
                int m = n - 1;
                // worker的queue肯定放在pool中的queue[]中的奇數下標
              	// m = ws.lenght - 1, ws.lenght 肯定是偶數,則m 肯定是奇數
                // 1的二進制位:00000001, 所以任何數 "|" 1 都是奇數
                // 所以 奇數 & 奇數 , 1&1 = 1,所以i肯定是奇數
                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;
                    // 如果下標已經有隊列,則重新生成奇數下標
                    // step肯定爲偶數:EVENMASK:0xfffe:1111111111111110
                  	// 所以 奇數+偶數,奇偶性不變
                    while (ws[i = (i + step) & m] != null) {
                        if (++probes >= n) {
                            workQueues = ws = Arrays.copyOf(ws, n <<= 1);
                            m = n - 1;
                            probes = 0;
                        }
                    }
                }
                //...
            }
        //......
    }

6、scan:

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
           	// k = r & m 。 r是一個隨機數,m 是 隊列數組長度 - 1;用於定位去哪個 隊列 竊取 task
            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) {
                  	// 如果有還沒執行的task,嘗試竊取隊列q 中的base下標的 task。 即FIFO
                    // i: 在內存中,b下標對應的對象的偏移值。 a.length - 1 的二進制位 永遠是 0[1...]s,所以 (a.length - 1) & b = b,主要是保證了b不會越界
                    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) {
                           	// ss 是小偷的 scanState,大於0代表當前的worker是激活的
                            if (ss >= 0) {
                              	// 把 task 從 隊列中取出來,然後隊列的base+1,如果被竊取的隊列中有多於1個的task,則嘗試喚醒其他的worker
                                if (U.compareAndSwapObject(a, i, t, null)) {
                                    q.base = b + 1;
                                    if (n < -1)       // signal others
                                        signalWork(ws, q);
                                    return t;
                                }
                            }
                          	// ss小於0代表當前的worker是未激活的,並且當前是第一次掃描,這時候嘗試激活worker
                            // oldSum: 上一次遍歷週期的 base 值的和。
                            // (int) c : 可以拿到當前棧頂的空閒worker。sp = (int) c
                            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;
                }
                // 每次沒有竊取到task的時候,都會k+1(k值不會超過m),當k遍歷了一圈還沒有steal到任務,則當前小偷worker是過剩的,則inactive這個小偷worker
                if ((k = (k + 1) & m) == origin) {    // continue until stable
                  	// oldSum == (oldSum = checkSum) 實際上就是 oldSum == checkSum , oldSum = checkSum
                    // oldSum == checkSum 是判斷 這個週期和上個週期 的base和是否一直,如果一直, 說明base可能沒有變過
                    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)));
                        // 維護 隊列的 stack,可以指向前一個棧頂的隊列
                        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;
    }

7、signalWork

    final void signalWork(WorkQueue[] ws, WorkQueue q) {
        long c; int sp, i; WorkQueue v; Thread p;
        // AC是負數,所以 active worker不足
        while ((c = ctl) < 0L) {                       // too few active
            // sp:第一位是0,沒有版本號,沒有inactive的worker
            if ((sp = (int)c) == 0) {                  // no idle workers
                //tc: tc不爲0,就是代表 total worker - parallelism < 0, 所以需要添加worker
                if ((c & ADD_WORKER) != 0L)            // too few workers
                    tryAddWorker(c);
                break;
            }
            if (ws == null)                            // unstarted/terminated
                break;
            // 取棧頂的worker,如果下標已經越界或queue爲null,線程池都是終止了
            if (ws.length <= (i = sp & SMASK))         // terminated
                break;
            if ((v = ws[i]) == null)                   // terminating
                break;
            // 新的scanState,版本+1,設置狀態爲激活,INACTIVE = 1 << 31,~INACTIVE = 01111111....
            int vs = (sp + SS_SEQ) & ~INACTIVE;        // next scanState
            // 確認 worker的 sp沒有變化
            int d = sp - v.scanState;                  // screen CAS
            // 生成新的 ctl,(UC_MASK & (c + AC_UNIT))設置 高32位, (SP_MASK & v.stackPred)設置低32位
            long nc = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & v.stackPred);
            if (d == 0 && U.compareAndSwapLong(this, CTL, c, nc)) {
                //激活worker
                v.scanState = vs;                      // activate v
                if ((p = v.parker) != null)
                    U.unpark(p);
                break;
            }
            //當前queue沒有task 需要執行了,則停止signal
            if (q != null && q.base == q.top)          // no more work
                break;
        }
    }

parallelStream的注意事項

  1. parallelStream.forEach是不保證順序的,如果要保證順序正確,則使用 forEachOrdered。在這裏插入圖片描述
    在這裏插入圖片描述
  2. parallelStream實際上使用的是多線程的方式,所以在運行的業務邏輯裏面要注意線程安全的問題
    在這裏插入圖片描述
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章