17、ForkJoinPool之工作线程的启动与任务的fork以及join

一、工作线程的启动

ForkJoinPool创建的工作线程类型为ForkJoinWorkerThread,下面是它的run方法

1.1 启动扫描任务

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 {
     
                //取消注册,在第16节分析过
                pool.deregisterWorker(this, exception);
            }
        }
    }
}

继续看到runWorker方法

final void runWorker(WorkQueue w) {
     
    //初始化任务列表,在创建WorkQueue时候没有初始化任务列表
    //这个方法在第15节分析WorkQueue讲过,不再赘述
    w.growArray();                   // allocate queue
    //获取创建WorkQueue使用的随机数
    int seed = w.hint;               // initially holds randomization hint
    int r = (seed == 0) ? 1 : seed;  // avoid 0 for xorShift
    for (ForkJoinTask<?> t;;) {
     
        //扫描任务:到工作队列组中随机获取工作队列,从里面获取任务执行
        if ((t = scan(w, r)) != null)
            //如果扫描到任务,执行
            w.runTask(t);
            //没有扫描到任务,尝试自旋或者挂起
        else if (!awaitWork(w, r))
            break;
        //修改随机值
        r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
    }
}

1.2 扫描任务

private ForkJoinTask<?> scan(WorkQueue w, int r) {
     
    WorkQueue[] ws; int m;
    //工作队列组不为空
    if ((ws = workQueues) != null && (m = ws.length - 1) > 0 && w != null) {
     
        //获取工作线程对应队列的scanState,高16位表示版本计数,低16表示队列在队列组中的索引下标
        int ss = w.scanState;                     // initially non-negative
        //r & m 随机一个下标,oldSum与checkSum用于比较校验和,如果两次校验和一样,那么说明工作队列组的任务至少在两次扫描任务
        //时没有发生大的变化,工作线程将会进入失活状态,进入失活状态后还有一次机会复活,如果继续发生至少2次校验和和失活前校验和相等情况,那么将会进入
        //自旋或者阻塞方法
        for (int origin = r & m, k = origin, oldSum = 0, checkSum = 0;;) {
     
            WorkQueue q; ForkJoinTask<?>[] a; ForkJoinTask<?> t;
            int b, n; long c;
            //随机下标找到一个不为null的工作队列
            if ((q = ws[k]) != null) {
     
                //如果队列中有任务
                if ((n = (b = q.base) - q.top) < 0 &&
                    (a = q.array) != null) {
           // non-empty
                    //FIFO,从栈底获取任务
                    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;
                                //工作队里中超过1个任务
                                if (n < -1)       // signal others
                                    //尝试添加或者唤醒其他工作线程来处理
                                    signalWork(ws, q);
                                return t;
                            }
                        }
                        //如果当前工作线程因为之前没有扫描到任务而失活,在复活机会中找到了机会复活就会调用tryRelease方法
                        //进行激活
                        else if (oldSum == 0 &&   // try to activate
                                 w.scanState < 0)
                            //复活线程
                            tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
                    }
                    //如果调用了tryRelease复活,那么需要更新scanState状态
                    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;
            }
            //(k + 1) & m表示移动到下一个坑位,如果遍历后所有的队列都没有任务,那么很快就遍历一圈
            //此时(k + 1) & m)等于上次检查的索引位置origin
            if ((k = (k + 1) & m) == origin) {
         // continue until stable
                //如果工作线程还没失活,并且至少两次自旋后其校验和相等,那么需要将工作线程标记为失活
                if ((ss >= 0 || (ss == (ss = w.scanState))) &&
                    oldSum == (oldSum = checkSum)) {
     
                    //在ss >= 0的情况下先要失活,然后再至少2次复活的机会
                    if (ss < 0 || w.qlock < 0)    // already inactive
                        break;
                    //INACTIVE = 1 << 31,第32位是1,所以它是一个负数,负数表示失活
                    int ns = ss | INACTIVE;       // try to inactivate
                    //减去一个活跃线程数,将失活后的scanState设置到ctl的低32位
                    long nc = ((SP_MASK & ns) |
                               (UC_MASK & ((c = ctl) - AC_UNIT)));
                    //用这个失活工作线程的工作队列记录上一个失活的工作线程scanState,形成一个栈,在ctl中表示栈顶
                    w.stackPred = (int)c;         // hold prev stack top
                    //更新当前工作线程的scanState
                    U.putInt(w, QSCANSTATE, ns);
                    //原子更新ctl
                    if (U.compareAndSwapLong(this, CTL, c, nc))
                        ss = ns;
                    else
                        //更新失败,回滚,继续扫描任务,因为ctl发生改变,有可能是因为有任务提交进来
                        //然后唤醒了ctl上阻塞的线程或者添加了工作线程
                        w.scanState = ss;         // back out
                }
                checkSum = 0;
            }
        }
    }
    return null;
}

随机一个下标扫描对应任务队列,如果经过至少两轮未获取到任务并且校验和一样(基本没变),那么工作线程将进入失活状态,进入失活状态后仍然至少有两次复活的机会,
如果复活成功将恢复激活状态,如果复活失败,将进入自旋或者阻塞方法

1.3 执行任务并处理本地任务

final void runTask(ForkJoinTask<?> task) {
     
    if (task != null) {
     
        //SCANNING = 1,scanState &= ~SCANNING将scanState变成了偶数,表示正在执行任务
        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
            //将工作线程中偷取的任务数叠加到ForkJoinPool的stealCounter中
            transferStealCount(pool);
        //恢复正在扫描状态
        scanState |= SCANNING;
        if (thread != null)
            //钩子函数
            thread.afterTopLevelExec();
    }
}

执行偷取到的任务,执行完毕后执行自身工作队列中的任务,最后将自身偷取的任务计数叠加到ForkJoinPool的stealCounter字段中

1.4 线程失活挂起

private boolean awaitWork(WorkQueue w, int r) {
     
    //如果队列已经被取消注册,直接返回
    if (w == null || w.qlock < 0)                 // w is terminating
        return false;
    //w.stackPred表示前一个挂起线程的scanState
    for (int pred = w.stackPred, spins = SPINS, ss;;) {
     
        //如果已经激活,退出
        if ((ss = w.scanState) >= 0)
            break;
        //自旋
        else if (spins > 0) {
     
            //使用随机值和自旋次数控制自旋
            //随机自旋
            r ^= r << 6; r ^= r >>> 21; r ^= r << 7;
            if (r >= 0 && --spins == 0) {
              // randomize spins
                WorkQueue v; WorkQueue[] ws; int s, j; AtomicLong sc;
                //检查前一个失活的工作线程是否已经复活,如果前一个线程还未挂起或者复活了,很有理由相信前一个工作线程可能会复活自己
                //在多线程的情况下,前一个失活线程可能因为没有获取到cpu时间片的缘故会比当前工作线程要晚来到这个方法,甚至因为有新
                //的任务提交获得了复活机会
                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
            }
        }
        //队列已经被注销
        else if (w.qlock < 0)                     // recheck after spins
            return false;
        else if (!Thread.interrupted()) {
     
            long c, prevctl, parkTime, deadline;
            //(int)((c = ctl) >> AC_SHIFT) 表示获取活跃线程数 - 总并行线程数
            //(config & SMASK)获取总并行线程数
            //两者相加得到 获取活跃线程数 - 总并行线程数 + 总并行线程数 = 活跃线程数
            int ac = (int)((c = ctl) >> AC_SHIFT) + (config & SMASK);
            
            //如果活跃线程数为零,可能是因为要关闭线程池,这里尝试去帮助关闭线程池
            if ((ac <= 0 && tryTerminate(false, false)) ||
                //已经STOP,退出循环,释放线程
                (runState & STOP) != 0)           // pool terminating
                return false;
            //如果活跃线程数为零,那么再检查下这个线程是否是最后挂起的活跃线程
            //如果是最后挂起的活跃线程,那么不能一直阻塞下去,需要设置挂起时间,要不然在没人
            //提交任务唤醒线程的情况下,这个线程池就相当于死掉了
            if (ac <= 0 && ss == (int)c) {
             // is last waiter
                //这里这个prevctl起这样的作用,如果当前工作线程属于超生线程或者队列确实已经很久没有提交过任务了
                //就需要适当削减工作线程,像极了公司裁员
                //如果当前工作线程是属于不需要的线程,那么在注销当前工作线程时需要将ctl的高16位也就是活跃线程数减1
                //所以这里需要加1,如果当前这个线程不需要丢弃,那么这个prevctl也不会使用
                prevctl = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & pred);
                //获取总线程数,高32中的低16位
                //在ForkJoinPool的构造器中,对于这个总线程个数,设置到ctl时使用的是一个负数
                //如果并行数为4,那么设置到ctl中的值就是-4
                int t = (short)(c >>> TC_SHIFT);  // shrink excess spares
                //此处获取的t大于2,那么说明在join任务时,可能进行了线程补偿措施
                //如果并行数为4,那么很明显这里已经 > 6了,对于超生的工作线程,将会被抛弃
                if (t > 2 && U.compareAndSwapLong(this, CTL, c, prevctl))
                    return false;                 // else use timed wait
                //计算挂起时间
                parkTime = IDLE_TIMEOUT * ((t >= 0) ? 1 : 1 - t);
                //计算deadline
                deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP;
            }
            else
                //永久挂起,值得注意的是,如果这种被挂起的线程被唤醒之后,如果对应的scanState还是失活状态
                //那么线程池正在关闭
                prevctl = parkTime = deadline = 0L;
            Thread wt = Thread.currentThread();
            //记录被谁阻塞
            U.putObject(wt, PARKBLOCKER, this);   // emulate LockSupport
            w.parker = wt;
            //阻塞
            if (w.scanState < 0 && ctl == c)      // recheck before park
                U.park(false, parkTime);
            U.putOrderedObject(w, QPARKER, null);
            U.putObject(wt, PARKBLOCKER, null);
            //复活
            if (w.scanState >= 0)
                break;
            //如果阻塞时间不为零,那么发生了所有线程都被挂起的情况,如果ctl没有发生过变更,那么说明在这段时间外部没有提交任务进来
            //那么就不需要那么多工作线程了,需要削减
            if (parkTime != 0L && ctl == c &&
                deadline - System.nanoTime() <= 0L &&
                U.compareAndSwapLong(this, CTL, c, prevctl))
                return false;                     // shrink pool
        }
    }
    return true;
}

在获取不到任务时,工作线程先检查是否需要自旋,如果需要自旋将与随机数一起配合,形成随机自旋,对于补偿线程过多的,将被释放,如果太久没有任务提交,会缩减线程

1.5 执行本地任务

final void execLocalTasks() {
     
    int b = base, m, s;
    ForkJoinTask<?>[] a = array;
    //队列中有任务
    if (b - (s = top - 1) <= 0 && a != null &&
        (m = a.length - 1) >= 0) {
     
        //如果是后进先出模式
        if ((config & FIFO_QUEUE) == 0) {
     
            for (ForkJoinTask<?> t;;) {
     
                //从栈顶取值
                if ((t = (ForkJoinTask<?>)U.getAndSetObject
                     (a, ((m & s) << ASHIFT) + ABASE, null)) == null)
                    break;
                U.putOrderedInt(this, QTOP, s);
                //执行任务
                t.doExec();
                if (base - (s = top - 1) > 0)
                    break;
            }
        }
        else
            //从栈底取值
            pollAndExecAll();
    }
}

栈底取值

final void pollAndExecAll() {
     
    //栈底取值
    for (ForkJoinTask<?> t; (t = poll()) != null;)
        t.doExec();
}


final ForkJoinTask<?> poll() {
     
    ForkJoinTask<?>[] a; int b; ForkJoinTask<?> t;
    //任务队列不为空
    while ((b = base) - top < 0 && (a = array) != null) {
     
        //从栈底取值
        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;
                }
            }
            else if (b + 1 == top) // now empty
                break;
        }
    }
    return null;
}

二、ForkJoinTask之fork and join

2.1 fork

public final ForkJoinTask<V> fork() {
     
    Thread t;
    //内部线程fork的任务直接push到自身工作对列中
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
        //直接调用工作线程对应的对列去存储任务
        ((ForkJoinWorkerThread)t).workQueue.push(this);
    else
        //外部线程调用的fork,将被push到通用ForkJoinPool池
        //externalPush方法是处理外部线程提交的任务,我们在第16节分析过
        ForkJoinPool.common.externalPush(this);
    return this;
}

2.2 join

public final V join() {
    int s;
    //doJoin()返回执行任务后的状态
    //DONE_MASK:任务状态掩码
    if ((s = doJoin() & DONE_MASK) != NORMAL)
        //处理错误
        reportException(s);
    //获取执行结果
    return getRawResult();
}

doJoin

private int doJoin() {
     
    int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
    //(s = status) < 0成立,表示任务已经完成
    //如果是内部线程join的任务,将交由内部线程去处理
    //外部线程join的任务,由通用ForkJoinPool去处理
    return (s = status) < 0 ? s :
        ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
        (w = (wt = (ForkJoinWorkerThread)t).workQueue).
        //尝试将栈顶任务置空,然后执行任务
        tryUnpush(this) && (s = doExec()) < 0 ? s :
        wt.pool.awaitJoin(w, this, 0L) :
        externalAwaitDone();
}

下面我们将doJoin中涉及到的方法重点分析下

2.2.1 tryUnpush

final boolean tryUnpush(ForkJoinTask<?> t) {
     
    ForkJoinTask<?>[] a; int s;
    //尝试将栈顶任务置空,如果t就是队列中的栈顶任务,那么尝试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;
}

工作线程在join任务时,首先看看这个任务是否被fork到了栈顶,如果fork到了栈顶那么很好办,直接尝试去执行这个任务,如果不是栈顶任务,可能开发者没有按照fork的逆序序去
join任务或者被其他工作线程给窃取了,那么将进入awaitJoin方法

2.2.2 awaitJoin

final int awaitJoin(WorkQueue w, ForkJoinTask<?> task, long deadline) {
     
    int s = 0;
    if (task != null && w != null) {
     
        //记录前一个正在join的任务
        ForkJoinTask<?> prevJoin = w.currentJoin;
        //记录join当前任务
        U.putOrderedObject(w, QCURRENTJOIN, task);
        //这是一个ForkJoinTask的实现,我们先不用理会这个是干啥的,这个感兴趣的话自个去看看就好
        CountedCompleter<?> cc = (task instanceof CountedCompleter) ?
            (CountedCompleter<?>)task : null;
        for (;;) {
     
            //任务已经执行完毕,不需要再自旋了,直接返回
            if ((s = task.status) < 0)
                break;
            //帮助完成,用于CountedCompleter,此处不会深究
            if (cc != null)
                helpComplete(w, cc, 0);
            //如果当前队列任务为空,为空说明当前任务被其他工作线程给窃取了
            //tryRemoveAndExec是用于尝试执行存到队列中的当前任务,应为这个任务可能不在栈顶
            //如果队列中没有找到当前join的这个任务,那很明显被其他工作线程给偷走了
            else if (w.base == w.top || w.tryRemoveAndExec(task))
                //找到窃取join任务的工作线程,帮助窃取者执行窃取者的任务(以牙还牙,礼尚往来的感觉)
                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;
}

检查当前工作线程的队列是否为空,如果为空那么说明join的那个任务被其他工作线程给偷走了,如果队列不为空,那么从队列中取找这个任务,如果这个任务能够找到,那么尝试去
执行它,如果队列中没有这个任务,那么说明这个任务其他工作线程给偷走了,需要去找到这个窃取者,然后窃取它的任务进行执行。最后还有补偿措施,这个后面再说。

2.2.3 tryRemoveAndExec

final boolean tryRemoveAndExec(ForkJoinTask<?> task) {
     
    ForkJoinTask<?>[] a; int m, s, b, n;
    if ((a = array) != null && (m = a.length - 1) >= 0 &&
        task != null) {
     
        //队列中是否存在任务
        while ((n = (s = top) - (b = base)) > 0) {
     
            for (ForkJoinTask<?> t;;) {
           // traverse from s to b
                //从栈顶开始往下取值
                long j = ((--s & m) << ASHIFT) + ABASE;
                //因为存在并发,可能会被窃取线程偷走任务
                if ((t = (ForkJoinTask<?>)U.getObject(a, j)) == null)
                    //如果发生了任务的窃取,那么说明此时的s已经执行到了栈底
                    //如果被偷走join任务是在栈顶被偷走的,那么将返回true
                    //但如果不是栈顶任务就返回false?不应该返回true吗?
                    return s + 1 == top;     // shorter than expected
                else if (t == task) {
     
                    boolean removed = false;
                    //当前join任务在栈顶,尝试将其弹出
                    //如果cas失败,任务被其他线程偷走,此时的队列已经空了
                    if (s + 1 == top) {
           // pop
                        if (U.compareAndSwapObject(a, j, task, null)) {
     
                            U.putOrderedInt(this, QTOP, s);
                            removed = true;
                        }
                    }
                    //当前join任务不在栈顶并且栈底没变,将当前join任务的坑位替换成EmptyTask对象
                    //如果栈底发生了变更,就不会执行这个分支的代码,这里也不太清楚为啥要这么搞,难道不能直接尝试替换成EmptyTask吗?
                    //如果当前任务被抢,对应的坑位也不会cas成功,表示已经任务被偷走了,为什么还要重新循环一遍呢?
                    //为了减少多线程cas的几率?
                    else if (base == b)      // replace with proxy
                        //因为任务不在栈顶,不能直接替换成null,替换成null就必须移动指针
                        //很显然这里不能移动指针
                        //很多地方都是以null作为并发判断,其他工作线程取到null时会认为任务被其他线程抢先了
                        //这样就永远获取不到任务了
                        removed = U.compareAndSwapObject(
                            a, j, task, new EmptyTask());
                    if (removed)
                        //执行任务
                        task.doExec();
                    break;
                }
                //如果其他任务已经执行完成,并且是栈顶任务,那么置空
                //通常我们的任务都是先取,然后cas将对应坑位置空,再去执行的,为什么这里会出现任务还在
                //队列中,任务却已执行完成的情况呢?前面我们看到如果当前join的任务不是在栈顶,那么这个会被EmptyTask
                //占位替换,这个EmptyTask的任务状态直接就是NORMAL(正常完成状态)
                else if (t.status < 0 && s + 1 == top) {
     
                    if (U.compareAndSwapObject(a, j, t, null))
                        U.putOrderedInt(this, QTOP, s);
                    break;                  // was cancelled
                }
                //从栈顶找到栈底,都没有找到,已经被别的工作线程偷走
                //不太清楚为啥这里要返回false,没找到不应该就是被其他线程偷走了吗?
                //偷走应该返回true,让当前线程去寻找偷取者,帮它执行它的任务
                if (--n == 0)
                    return false;
            }
            //任务已经完成
            if (task.status < 0)
                return false;
        }
    }
    return true;
}

上面的方法尝试去队列中寻找那个要join的任务,如果队列为空的,直接返回true,去寻找偷取任务者,如果队列不为空,那么从栈顶往栈底寻找,如果找到并且是栈顶任务,
直接自己执行,如果不是栈顶任务,还要检查一下栈底是否发生改变,也就是有没有其他工作线程来偷任务,如果任务被偷,那么通过cas替换成EmptyTask,然后执行任务。
其他情况返回false。

2.2.4 helpStealer

private void helpStealer(WorkQueue w, ForkJoinTask<?> task) {
     
    WorkQueue[] ws = workQueues;
    int oldSum = 0, checkSum, m;
    //队列组和任务队列不为空
    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队列在队列组中的用于计算下标的随机值,如果找到了偷取者这个值会变成对应偷取者的下标
                //j.hint | 1 将值变成奇数
                //k += 2 步长为2,奇数加2,还是奇数,从二进制来看,这个2是第二个bit为1,所以不会改变第一个bit位
                for (int h = j.hint | 1, k = 0, i; ; k += 2) {
     
                    //索引已经超出工作队列组都没有找到偷取者,退出
                    //此时的任务可能已经完成
                    if (k > m)                     // can't find stealer
                        break descent;
                    //(h + k) & m 计算下标,检查这个下标的队列是否偷走了自己的任务
                    if ((v = ws[i = (h + k) & m]) != null) {
     
                        //小偷是不是你
                        if (v.currentSteal == subtask) {
     
                            //是的,记录这个偷取者在队列组的下标
                            j.hint = i;
                            break;
                        }
                        //将沿途排查过队列栈底计入校验和
                        checkSum += v.base;
                    }
                }
                for (;;) {
                              // help v or descend
                    ForkJoinTask<?>[] a; int b;
                    //将偷取者线程队列栈底也计入校验和,因为它偷取了任务,很有可能fork出更小的任务然后被其他线程偷走
                    checkSum += (b = v.base);
                    //获取偷取者当前正在join的任务
                    ForkJoinTask<?> next = v.currentJoin;
                    //subtask.status < 0 任务执行完成
                    //j.currentJoin != subtask 当前要join的任务已经不是上一个任务了,通常发生在当前工作线程去帮助偷取者A去找偷取者A的偷取者时
                    //j被赋值为偷取者A的队列,偷取者A队列的currentJoin随时会因为任务完成而被偷取者A线程修改
                    //v.currentSteal != subtask 偷取者已经执行完当前任务,偷了其他任务执行
                    if (subtask.status < 0 || j.currentJoin != subtask ||
                        v.currentSteal != subtask) // stale
                        break descent;
                    //偷取者队列没有任务可取,也就是执行偷取到的任务没有继续fork任务,或者偷取者就是已经把任务执行完了或者没有执行到fork代码等等
                    if (b - v.top >= 0 || (a = v.array) == null) {
     
                        //偷取者没有任务join,退出去判断任务是否完成,校验和是否不一样
                        if ((subtask = next) == null)
                            break descent;
                        //如果偷取者有任务要join,那么将帮着这个偷取者去找它的偷取者
                        j = v;
                        break;
                    }
                    //偷取偷取者的任务(也有可能是自己被偷的任务fork出来的子任务),从栈底开始
                    int i = (((a.length - 1) & b) << ASHIFT) + ABASE;
                    ForkJoinTask<?> t = ((ForkJoinTask<?>)
                                         U.getObjectVolatile(a, i));
                    //栈底是否发生变更,如果发生变更,那么当前获取的偷取者的栈底任务已经被其他线程偷了
                    //避免无效的cas,直接重新来过
                    if (v.base == b) {
     
                        //等于null,表示任务已经被其他线程抢占了,然后赋值成了null,只是还没来得及将base更新
                        if (t == null)             // stale
                            break descent;
                        //cas
                        if (U.compareAndSwapObject(a, i, t, null)) {
     
                            v.base = b + 1;
                            //记录自己前一个偷取的任务
                            ForkJoinTask<?> ps = w.currentSteal;
                            int top = w.top;
                            do {
     
                                //将新偷到的任务更新到currentSteal中
                                U.putOrderedObject(w, QCURRENTSTEAL, t);
                                //执行任务
                                t.doExec();        // clear local tasks too
                                //在join的任务还未执行完成的情况下,并且刚才执行的任务发生了fork任务,那么
                                //w.top != top就会成立,此时就得w.pop()执行本地任务
                            } while (task.status >= 0 &&
                                     w.top != top &&
                                     (t = w.pop()) != null);
                            //恢复
                            U.putOrderedObject(w, QCURRENTSTEAL, ps);
                            //w.base != w.top成立表示自己还有任务要执行,没必要继续帮别人干活了
                            if (w.base != w.top)
                                return;            // can't further help
                        }
                    }
                }
            }
            //任务为执行完毕,临近两次的校验和都一样,说明没有太大变化,没必要继续了
        } while (task.status >= 0 && oldSum != (oldSum = checkSum));
    }
}

为了不让工作线程空闲,作者让这个工作线程尽量发挥作用去寻找偷取了它任务的偷取者,帮助它去执行任务,因为偷取的任务有可能会继续fork出子任务,所以它去偷取这些
fork出来的任务反倒加快了任务的执行,这样看起来,偷似乎又有点协助的味道。

2.2.5 tryCompensate

private boolean tryCompensate(WorkQueue w) {
     
    boolean canBlock;
    WorkQueue[] ws; long c; int m, pc, sp;
    //队列是否被注销
    if (w == null || w.qlock < 0 ||           // caller terminating
        (ws = workQueues) == null || (m = ws.length - 1) <= 0 ||
        (pc = config & SMASK) == 0)           // parallelism disabled
        canBlock = false;
    //如果有挂起的线程,释放它,在一定程度上也许会执行到自己被偷任务fork出的子任务
    //加快任务的执行,tryRelease方法的第二参数为0
    //当唤醒成成功时,就意味着当前工作线程将被阻塞,新的空闲线程被唤醒,所以没必要先减少活跃线程数,然后再加上
    else if ((sp = (int)(c = ctl)) != 0)      // release idle worker
        canBlock = tryRelease(c, ws[sp & m], 0L);
    else {
     
        //获取活跃线程数
        int ac = (int)(c >> AC_SHIFT) + pc;
        //获取总线程数
        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;
            //找奇数小标的队列,这么看来循环m次就是执行了两遍,为什么执行两遍呢?主要是为了判断稳定性,有可能第二遍的时候,正在处理任务的
            //工作线程又少了也不一定
            if ((v = ws[((i << 1) | 1) & m]) != null) {
     
                //检查工作线程是否正在处理任务,如果不在处理任务表示空闲,可以获取其他任务执行
                if ((v.scanState & SCANNING) != 0)
                    break;
                ++nbusy;
            }
        }
        //由于上面进行两次奇数位工作线程的记录,此处的nbusy与总线程的2被比较,如果
        //不相等,说明存在空闲或者还在扫描任务的工作线程
        // ctl != c,ctl发生改变,有可能线程执行完任务后,没有扫描到新的任务被失活
        //此时先不挂起,先自旋下看看自己join的任务是否完成了
        if (nbusy != (tc << 1) || ctl != c)
            canBlock = false;                 // unstable or stale
        //tc >= pc,总线程数大于并行数说明此时工作线程数已经够多了,当然并不是代表一定不会再创建新的工作线程了
        //ac > 1,活跃线程数大于1说明至少除了自己外还有一个线程正在处理任务(有可能是正在处理自己被偷的任务),它的存在还可以唤醒其他沉睡的线程
        //不至于在没有外部线程干扰下大家都睡死了
        //w.isEmpty(),当前工作线程队列中没有任务需要执行了,既然没有任务要执行,那么就不需要找其他的补偿线程来处理任务。
        //直接减少活跃线程数,表示自己一旦cas更改ctl成功,就要去睡觉了
        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);
        }
        //总线程数太多了,不能在创建补偿工作线程了
        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
            //下面准备创建新的工作线程,这里只加总线程数,不加活跃线程数是因为当前工作线程将在创建补偿线程成功之后阻塞
            long nc = ((AC_MASK & c) |
                       (TC_MASK & (c + TC_UNIT)));
            //线程池没有STOP的情况下才允许创建新的工作线程
            if (((rs = lockRunState()) & STOP) == 0)
                add = U.compareAndSwapLong(this, CTL, c, nc);
            unlockRunState(rs, rs & ~RSLOCK);
            //创建新的工作线程
            canBlock = add && createWorker(); // throws on exception
        }
    }
    return canBlock;
}

上面的补偿方法在工作线程准备wait之前会尝试找一个替补线程去帮忙处理任务:

  • 有没有被挂起的工作线程,如果有,那么就唤醒这个被挂起的工作线程,如果没有继续往下
  • 判断自己的任务队列是否为空,当前总工作线程是否饱和,是否还存在出自己之外的活跃线程,如果这些条件都满足,那么就没必要创建额外的线程去处理任务
  • 总线程太多,已经达到MAX_CAP,此时不能再创建新的工作线程了
  • 检查ForJoinPool是否已经关闭,如果没有被关闭,创建新的工作线程

2.2.6 externalAwaitDone

private int externalAwaitDone() {
     
    //本次不讲CountedCompleter
    int s = ((this instanceof CountedCompleter) ? // try helping
             ForkJoinPool.common.externalHelpComplete(
                 (CountedCompleter<?>)this, 0) :
                 //尝试从栈顶获取到当前join的任务
             ForkJoinPool.common.tryExternalUnpush(this) ? doExec() : 0);
    if (s >= 0 && (s = status) >= 0) {
     
        boolean interrupted = false;
        do {
     
            //阻塞前,先设置SIGNAL标记,以便告诉其他线程我需要唤醒
            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;
}

外部线程的提交比较简单,首先先看看当前join的任务是不是在栈顶,如果是在栈顶,那么锁住共享队列将任务取出然后执行,其他的情况挂起。

二、ForkJoinPool的关闭

private boolean tryTerminate(boolean now, boolean enable) {
     
    int rs;
    //通用线程池,不会被关闭
    if (this == common)                       // cannot shut down
        return false;
    //线程池是否正在运行,如果正在运行,检查enable是否设置了为true,如果是false不会做任何操作
    //这是因为有些地方会调用这个方法尝试帮助关闭线程池,如果线程池确实已经关闭了,那么runState不会大于零,enable也就不会使用
    if ((rs = runState) >= 0) {
     
        if (!enable)
            return false;
        rs = lockRunState();                  // enter SHUTDOWN phase
        //进入线程池关闭节点,首先先把SHUTDOWN标记打上
        unlockRunState(rs, (rs & ~RSLOCK) | SHUTDOWN);
    }
    //是否已经STOP了,如果已经STOP了(rs & STOP) == 1
    if ((rs & STOP) == 0) {
     
        //在调用shutdownNow方法时,这个now为true,那么直接会打上STOP标记
        if (!now) {
                                // check quiescence
            for (long oldSum = 0L;;) {
             // repeat until stable
                WorkQueue[] ws; WorkQueue w; int m, b; long c;
                //以ctl作为初始校验和
                long checkSum = ctl;
                //检查活跃线程数,如果大于零,那么还不能直接设置为STOP
                if ((int)(checkSum >> AC_SHIFT) + (config & SMASK) > 0)
                    return false;             // still active workers
                //没有初始化或者任务队列都被注销了,可以STOP了
                if ((ws = workQueues) == null || (m = ws.length - 1) <= 0)
                    break;                    // check queues
                for (int i = 0; i <= m; ++i) {
     
                    //循环每个工作队列
                    if ((w = ws[i]) != null) {
     
                        //队列中有任务,队列处于激活状态,唤醒那些挂起的线程尽力去处理掉任务
                        if ((b = w.base) != w.top || w.scanState >= 0 ||
                            w.currentSteal != null) {
     
                            tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
                            return false;     // arrange for recheck
                        }
                        //以栈底作为校验和
                        checkSum += b;
                        //将偶数下标的工作队列(外部共享队列)取消
                        if ((i & 1) == 0)
                            w.qlock = -1;     // try to disable external
                    }
                }
                //两次校验和一样的,表示任务队列都已经空了,也没有新的工作队里创建啥的,可以STOP了
                if (oldSum == (oldSum = checkSum))
                    break;
            }
        }
        //STOP线程池
        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;
        //已经没有工作线程了,或者队列已经注销
        if ((short)(checkSum >>> TC_SHIFT) + (config & SMASK) <= 0 ||
            (ws = workQueues) == null || (m = ws.length - 1) <= 0) {
     
            //给线程池标记上TERMINATED
            if ((runState & TERMINATED) == 0) {
     
                rs = lockRunState();          // done
                unlockRunState(rs, (rs & ~RSLOCK) | TERMINATED);
                synchronized (this) {
      notifyAll(); } // for awaitTermination
            }
            break;
        }
        for (int i = 0; i <= m; ++i) {
     
            if ((w = ws[i]) != null) {
     
                checkSum += w.base;
                w.qlock = -1;                 // try to disable
                if (pass > 0) {
     
                    //取消任务
                    w.cancelAll();            // clear queue
                    //中断线程,唤醒被挂起的线程
                    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
                    }
                }
            }
        }
        //检查校验和,如果临近两次的校验和一样,说明队列已经没啥变化了,趋于稳定
        if (checkSum != oldSum) {
                  // unstable
            oldSum = checkSum;
            pass = 0;
        }
        //确定线程池已经趋于稳定,队列与任务被取消,挂起的线程已经被唤醒被中断
        else if (pass > 3 && pass > m)        // can't further help
            break;
        //第二阶段开始,去唤醒所有因失活被挂起的线程
        else if (++pass > 1) {
                     // try to dequeue
            long c; int j = 0, sp;            // bound attempts
            while (j++ <= m && (sp = (int)(c = ctl)) != 0)
                tryRelease(c, ws[sp & m], AC_UNIT);
        }
    }
    return true;
}

先将线程池设置为SHUTDOWN,如果线程池已经没啥活着的线程了并且工作队列总确实也没啥任务了,那么将进入STOP状态,进入STOP状态后分4个阶段:

  • 将队列标记为注销,w.qlock = -1;
  • 取消队列中还未执行的任务
  • 唤醒因为失活而挂起的线程
  • 中断线程,唤醒scanState小于0的工作线程,确保那些失活但还没来得及挂起的线程也能够得到唤醒信号

三、总结

本节主要分析了ForJoinPool线程池工作线程的执行逻辑还有任务的fork与join,fork比较简单,仅仅是将fork出的任务push到自己的工作队列中,join比较复杂,代码量比较巨
大,但主要思想就是从队列中找到这个被join的任务,找到它并执行它,但是为了尽量不浪费cpu资源,任务有可能被其他的工作线程偷取,任务被偷取了,那当前这个线程怎么
办,它当然不能干等着啊,它要去找到偷它任务的线程,帮助偷它任务的线程去执行其fork出的子任务,如果偷它任务的线程fork出的子任务又被其他线程偷走了,那么当前线
程会继续追踪偷小偷任务的小偷(其实用协助这个词会不会更好点),追踪到后处理这个偷小偷的小偷fork出的任务,依次类推。如果说小偷那没有fork出子任务,那没办法,
为了节省CPU资源不能一直耗下去,将进入阻塞阶段,阻塞时会有一个补偿措施(因为当前线程要join某个任务,需要等待,那么其他任务就没法执行,为了能够及时处理其他的
任务,此处有个补偿措施),如果当前ForkJoinPool有挂起的线程,那么唤醒它即可,如果没有挂起的线程,在满足一些条件之后创建一个新的工作线程去处理其他任务。

你可能感兴趣的:(JDK源码系列)