JUC之十:ForkJoinPool任务分割与等待

JUC之十:ForkJoinPool任务分割与等待

一、fork分割方法

    public final ForkJoinTask<V> fork() {
        Thread t;
        1、查看是否是ForkJoinWorkerThread类型的直接放入到当前workQueue的task数组中
        if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
            ((ForkJoinWorkerThread)t).workQueue.push(this);
        else
            2、否则按照外部提交的任务执行
            ForkJoinPool.common.externalPush(this);
        return this;
    }

这儿延申一个题外话:

@Override
        protected Long compute() {
            int middle = (from + to) / 2;
            SumTask taskLeft = new SumTask(numbers, from, middle);
            SumTask taskRight = new SumTask(numbers, middle + 1, to);
            //taskLeft.fork();
            //taskRight.fork();
            invokeAll(taskLeft, taskRight);
            return taskLeft.join() + taskRight.join();
        }

如上所示,究竟是 taskLeft.fork();+taskRight.fork(); 效率高还是使用invokeAll(taskLeft, taskRight);效率高。

1、invokeAll

    public static void invokeAll(ForkJoinTask<?> t1, ForkJoinTask<?> t2) {
        int s1, s2;
        t2.fork();
        if ((s1 = t1.doInvoke() & DONE_MASK) != NORMAL)
            t1.reportException(s1);
        if ((s2 = t2.doJoin() & DONE_MASK) != NORMAL)
            t2.reportException(s2);
    }

    private int doInvoke() {
        int s; Thread t; ForkJoinWorkerThread wt;
        return (s = doExec()) < 0 ? s :
            ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
            (wt = (ForkJoinWorkerThread)t).pool.
            awaitJoin(wt.workQueue, this, 0L) :
            externalAwaitDone();
    }

这儿可以看到只是将任务t2 fork了,任务t1直接执行了,然后t2进行doJoin。

所以invokeAllfork对比少了push到任务数组的一步,效率稍微高一点

二、join等待方法

    public final V join() {
        int s;
        1、doJoin是一个等待任务执行完毕的方法,当任务执行完毕就会返回
            等待但是不一定阻塞,后面讲解。若DONE_MASK不是NORMAL说明出现异常,进行报告异常
        if ((s = doJoin() & DONE_MASK) != NORMAL)
            reportException(s);
        2、获取结果返回
        return getRawResult();
    }

1、doJoin方法

    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 :
            wt.pool.awaitJoin(w, this, 0L) :
            externalAwaitDone();
    }

太难看了,换一种形式分析

private int doJoin() {
  int s;
  Thread t;
  ForkJoinWorkerThread wt;
  ForkJoinPool.WorkQueue w;
 1、status状态如果小于0就说明执行完了,有三种可能完成、取消或异常,反正就是任务结束了
  if((s = status) < 0) {
    return s;
  }else {
     2、如果是ForkJoinWorkerThread
    if((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) {
        2.1、取出该任务,并执行,返回结果
      if((w = (wt = (ForkJoinWorkerThread) t).workQueue).tryUnpush(this)&& (s = doExec()) < 0) { 
        return s;
      }else {
        2.2、否则有可能是其他的线程拿走了该任务,进行等待,但并不是阻塞
        return wt.pool.awaitJoin(w, this, 0L); 
      }
    }else { 
       3、不是ForkJoinWorkerThread,执行外部的等待方法
      return externalAwaitDone();
    }
  }
}
  • 若任务执行完毕直接返回执行状态
  • 没有结束观察是否是ForkJoinWorkerThread类型的,从而进入不同的等待策略。

2、externalAwaitDone 外部等待

    private int externalAwaitDone() {
        1、如果是CountedCompleter类型的,调用他的执行方法
        int s = ((this instanceof CountedCompleter) ? // try helping
                 ForkJoinPool.common.externalHelpComplete(
                     (CountedCompleter<?>)this, 0) :
                 ForkJoinPool.common.tryExternalUnpush(this) ? doExec() : 0);
        2、task还没有执行结束
        if (s >= 0 && (s = status) >= 0) {
            boolean interrupted = false;
            do {
                3、设置status为需要被唤醒状态
                if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) {
                    synchronized (this) {
                        3.1、再次检查task任务执行状态,进入等待
                        if (status >= 0) {
                            try {
                                wait(0L);
                            } catch (InterruptedException ie) {
                                interrupted = true;
                            }
                        }
                        else
                            3.2、发现task完成,则唤醒睡眠的线程
                            notifyAll();
                    }
                }
            } while ((s = status) >= 0);//检查task状态继续自旋
            if (interrupted)
                4、记录中断状态,添加上中断状态
                Thread.currentThread().interrupt();
        }
        return s;
    }

外部提交的任务逻辑较为简单,没执行完直接阻塞等待完成,获取结果。

3、tryUnpush

        final boolean tryUnpush(ForkJoinTask<?> t) {
            ForkJoinTask<?>[] a; int s;
            1、任务在顶部,通过cas直接置空该任务,将top-1,返回ture,否则返回false
            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;
        }

从任务数组中移除任务成功返回ture,下一步就直接执行任务了,否则返回false

4、awaitJoin方法

final int awaitJoin(WorkQueue w, ForkJoinTask<?> task, long deadline) {
        int s = 0;
        if (task != null && w != null) {
            1、记录一下prevJoin,当执行完之后恢复前一个
            ForkJoinTask<?> prevJoin = w.currentJoin;
            1.1、设置为当前的task
            U.putOrderedObject(w, QCURRENTJOIN, task);
            CountedCompleter<?> cc = (task instanceof CountedCompleter) ?
                (CountedCompleter<?>)task : null;
            for (;;) {
                2、该任务已经执行完了
                if ((s = task.status) < 0)
                    break;
                //针对CountedCompleter类型的
                if (cc != null)
                    helpComplete(w, cc, 0);
                3、当前队列没有任务了或还有任务但当前任务被窃取了
                else if (w.base == w.top || w.tryRemoveAndExec(task))
                    3.1、帮助窃取
                    helpStealer(w, task);
                4、再次确认该任务是否执行结束
                if ((s = task.status) < 0)
                    break;
                long ms, ns;
                5、阻塞的时间,在join方法中deadline是0一直等待
                if (deadline == 0L)
                    ms = 0L;
                else if ((ns = deadline - System.nanoTime()) <= 0L)
                    break;
                else if ((ms = TimeUnit.NANOSECONDS.toMillis(ns)) <= 0L)
                    ms = 1L;
                6、执行到这儿说明任务还未完成且工作队列还有任务,补偿策略(找一个替代的线程执行任务)
                   自己则进入等待状态
                if (tryCompensate(w)) {
                    6.2、补偿成功,等待指定的时间
                    task.internalWait(ms);
                    6.2、活跃线程+1
                    U.getAndAddLong(this, CTL, AC_UNIT);
                }
            }
            7、当前任务执行成功设置为task
            U.putOrderedObject(w, QCURRENTJOIN, prevJoin);
        }
        return s;
    }

该方法执行结束,任务返回执行状态

  • 当前join的任务执行结束就返回执行状态
  • 若当前任务被其他线程窃取了,则帮助窃取,反之找到一个替代的线程执行,自己进入等待状态。

5、tryRemoveAndExec

final boolean tryRemoveAndExec(ForkJoinTask<?> task) {
            ForkJoinTask<?>[] a; int m, s, b, n;
            if ((a = array) != null && (m = a.length - 1) >= 0 &&
                task != null) {
              	1、从顶部遍历到低端
                while ((n = (s = top) - (b = base)) > 0) {
                    for (ForkJoinTask<?> t;;) {      // traverse from s to b
                        2、顶部前一个位置的索引
                        long j = ((--s & m) << ASHIFT) + ABASE;
                        if ((t = (ForkJoinTask<?>)U.getObject(a, j)) == null)
                            2.1、如果该位置为null,并且s+1==top说明没有任务了,当前任务被窃取了
                            return s + 1 == top;     // shorter than expected
                        else if (t == task) {
                            3、t是当前的task
                            boolean removed = false;
                            if (s + 1 == top) {      // pop
                                3.1、s+1==pop,说明自己就是栈顶的任务,把该位置设置为null直接执行
                                if (U.compareAndSwapObject(a, j, task, null)) {
                                    U.putOrderedInt(this, QTOP, s);
                                    removed = true;
                                }
                            }
                            else if (base == b)      // replace with proxy
                                4、task在base到top-2的位置里,并且任务没有被窃取
                                cas设置task为EmptyTask,且status是正常执行结束,其实就是不会让该任务重复执行
                                removed = U.compareAndSwapObject(
                                    a, j, task, new EmptyTask());
                            if (removed)
                                //满足上述3或4任意一种任务没有被窃取,直接执行任务
                                task.doExec();
                            break;
                        }
                        5、t不是当前task,t执行完毕并且此时任务栈没有任务了,说明task被窃取执行了
                        else if (t.status < 0 && s + 1 == top) {
                            5.1、清除任务t
                            if (U.compareAndSwapObject(a, j, t, null))
                                U.putOrderedInt(this, QTOP, s);
                            break;                  // was cancelled
                        }
                        6、遍历完了,返回false不用帮助执行
                        if (--n == 0)
                            return false;
                    }
                    7、检查task是否执行结束,结束也无需帮助执行
                    if (task.status < 0)
                        return false;
                }
            }
    		8、需要帮助窃取执行
            return true;
        }

从top位置开始向下遍历任务,如果找到给定任务,把它从当前Worker的任务队列中移除并执行,移除的位置使用EmptyTask代替。如果任务队列为空或者任务未执行完毕返回true;任务执行完毕返回false。

6、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;
                1、这儿两个比较重要的变量j和v
                    j:当前线程所在的workQueue
                    v:窃取线程所在的workQueue
                WorkQueue j = w, v;                    // v is subtask stealer
                2、这儿将任务赋值给了subtask,并且任务没有结束
                descent: for (subtask = task; subtask.status >= 0; ) {
                    3、只遍历奇数位索引,也就是存在线程的workQueue,观察是否能找到窃取者
                    for (int h = j.hint | 1, k = 0, i; ; k += 2) {
                        3.1、遍历一圈但是没有找到回到descent结束外层的for循环
                        if (k > m)                     // can't find stealer
                            break descent;
                        if ((v = ws[i = (h + k) & m]) != null) {
                            3.2、找到了窃取者,记录一下窃取的索引方便下次查找
                            if (v.currentSteal == subtask) {
                                j.hint = i;
                                break;
                            }
                            3.3、这儿记录本次外层while循环是否发生了线程不安全的因素也可以称为线程扰动
                                若是则继续循环
                            checkSum += v.base;
                        }
                    }
                    4、执行到这儿说明已经找到了本次join任务的窃取者 就是v 接下来要帮助窃取者执行任务
                    for (;;) {                         // help v or descend
                        ForkJoinTask<?>[] a; int b;
                        checkSum += (b = v.base);
                        5、记录当前窃取线程的currentJoin
                        ForkJoinTask<?> next = v.currentJoin;
                        6、这儿窃取链断了,重新进入while循环
                        if (subtask.status < 0 || j.currentJoin != subtask ||
                            v.currentSteal != subtask) // stale
                            break descent;
                        7、窃取任务的队列没有任务了
                        if (b - v.top >= 0 || (a = v.array) == null) {
                            7.1、窃取的任务没有join,说明该任务被其他线程窃取了返回while循环
                            if ((subtask = next) == null)
                                break descent;
                            7.2、窃取的任务join了,注意这儿将窃取这赋值给了j
                                这样窃取者在下次for循环中就成为了主任务,该找窃取者的窃取者了
                            j = v;
                            break;
                        }
                        8、执行到这儿说明窃取者任务列表不为null,准备要帮助窃取者执行任务了
                            获取窃取者base端的任务
                        int i = (((a.length - 1) & b) << ASHIFT) + ABASE;
                        ForkJoinTask<?> t = ((ForkJoinTask<?>)
                                             U.getObjectVolatile(a, i));
                        8.1、窃取者没有被其他线程窃取
                        if (v.base == b) {
                            8.2、base端没有任务了,可能被别的线程拿走了跳到外层循环找到窃取的线程
                            if (t == null)             // stale
                                break descent;
                            8.3、设置i的位置为null,准备执行任务
                            if (U.compareAndSwapObject(a, i, t, null)) {
                                //base++被窃取了
                                v.base = b + 1;
                                8.4、记录调用者之前偷取的任务,后面递归会导致其改变,执行结束要还原
                                ForkJoinTask<?> ps = w.currentSteal;
                                //记录顶端,后续判断是否进入了新的任务
                                int top = w.top;
                                do {
                                    9、设置当前窃取的任务为t
                                    U.putOrderedObject(w, QCURRENTSTEAL, t);
                                    10、这儿就会进入我们实现的compute方法若产生子任务join会递归执行
                                    t.doExec();        // clear local tasks too
                                11while循环条件主任务还没执行完毕,自己任务队列不为null,优先处理自己的
                                } while (task.status >= 0 &&
                                         w.top != top &&
                                         (t = w.pop()) != null);
                                U.putOrderedObject(w, QCURRENTSTEAL, ps);
                           12、上面while循环刚结束,发现又来任务了,返回执行自己的任务,不在帮助Steal执行任务
                                if (w.base != w.top)
                                    return;            // can't further help
                            }
                        }
                    }
                }
                13、主任务未结束、且任务还在流动重新自旋帮助执行
            } while (task.status >= 0 && oldSum != (oldSum = checkSum));
        }
    }

该方法执行机制极其复杂有点难以理解,往大方向说就是 帮助窃取者stealer执行任务 ,算是一种优化,若没有该方法一样能转的起来。

有几种情况:

1、stealer 任务队列有任务,主线程就帮助执行其他任务,不过若发现当前线程所在workQueue也有任务了就优先执行自己的任务。

2、stealer 任务队列没有任务了,且窃取者当前join的任务也被窃取了,主线程帮助窃取者的窃取者执行任务。

3、其他情况主线程一直在这空转,什么也不做等待主任务执行结束。

主要关注几个变量:

  • j:当前线程join任务的所在workQueue,j一开始是主线程所在队列,但经过上述步骤7.2 之后,j就变成窃取者所在队列了,一次循环往复…,就可能发生如下场景:
    主线程join----->窃取者,发现窃取者也在join,窃取者join的任务被窃取了----->主线程帮助窃取者的窃取者…
  • v:当前窃取者所在工作队列。
  • subtask:一开始是主任务,若 stealer 任务队列没有任务了,subtask成为窃取者的join。

先说一下大致执行流程:

1、首先明确入参的w、task,这儿是主任务所在workQueue,以及主任务。

2、遍历workQueue数组奇数位索引,也就是内部任务所在的workQueue,定位到偷取者,并记录hint位置方便下次查找偷取者。(步骤3)

3、获取窃取者任务列表,帮助其执行任务,执行过程中发现自己任务队列有任务优先执行自己的,执行完自己的,再次发现自己队列又来任务了w.base != w.top ,不能继续帮助了,执行完主任务赶紧执行自己任务队列的任务了。(步骤8-12)

4、如果偷取者任务队列为null,帮助偷取者join任务,也就是帮助偷取者的偷取者执行任务。(步骤7)

5、最后判断任务task是否结束,如果未结束,且工作队列base和在变动中,说明偷取任务一直在进行,则重复以上操作,加快任务执行。

下面举一个场景分析一下:

1、thread 1在task 1上join了

2、遍历发现thread 2窃取了task1,既 thread 2.currentSteal == task 1

3、thread 1 从 thread 2 base端窃取一个任务…,同时也会优先执行自己的任务

4、thread 1 又从 thread 2 base端窃取一个任务…,同时也会优先执行自己的任务

5、thread 2 没有任务了,thread 1 继续寻找帮助 thread 2.currentJoin的任务,假如说找到了是thread 3

6、thread 1 就从 thread 3 base端窃取一个任务…,同时也会优先执行自己的任务…

7、一直循环下去,当然期间会有各种条件会打断。

最后在说一点,help stealer 虽然也会执行子任务,但它在功能上不是必须的,而是一个性能优化,优化点在于减少线程因为 join 而阻塞,join 期间帮其它线程执行一个任务,可能 join 的任务就执行完了。

三、总结

1、fork将任务放到当前线程所在的工作队列

2、join该任务若在top处,则拿出来直接执行,否则进入等待策略。

3、若task没有被窃取,则在自己的任务栈中肯定能找到任务,则直接执行;若被窃取了则走帮助窃取的路线,帮助窃取task的线程执行其他任务,而不是自己一直等着。

4、执行完之后发现任务还么有结束,则自己就要进入阻塞状态等待任务执行完毕唤醒当前线程。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值