如果把归并算法的递归过程,想象为一棵树,那么每个节点就是一个任务task,其中,父节点的结果需要子节点先计算了,才能计算出来。
好,说到树,树的遍历应该有所了解,必须先计算出子节点结果,再计算父节点,后序遍历,就可以做,但后序遍历还是在一个线程中运行,如何做到多线程并行遍历树呢?
这应该就是ForkJoinPool 以及 ForkJoinTask 等共同要解决的问题。
-
1.在ForkJoin中,Fork 意味着新的子节点出现,新的Task产生。如何让这些Task并发处理,没错偷窃算法,别的线程偷了该task,在别的线程运行,达到并行的效果。
- 1.为了避免疑惑,需要先解释一个概念,新的Task产生,会将其放在一个WorkQueue的队列中,而这个队列,可以是先进先出,也可以是先进后出.
-
2.Join 代表要执行该task
- Join 结束意味着拿到子节点task的结果, 反之, 子节点task还没计算完,Join是没法退出的,
- 这就意味着Join中要处理很多事,不然太浪费线程了. 它就不再像之前的get()方法,会一直循环等待结果.这里的Join像是一边等结果,一边做事,从自己对应的WorkQueue的队列里拿task 或者从别的Work Queue中偷task. 有点像,单线程的递归,没有结果就继续跑子节点的任务,直到任务有结果反馈,更何况这里还可以偷task.
- 这里的task中有一个status来表示自己状态, 当status<0,意味着运行结束或出了异常或取消了等,基本上就可以退出Join了
-
3.为什么可以偷?
- ForkJoinPool 有一个WorkQueues数组,单一的WorkQueue,有自己的对应的pool和线程及任务队列
也就是说,线程可以访问pool中的WorkQueue组,也就可以偷task. - 每个task 都status ,上面提及过,也方便了"偷",因为task运行在别的线程,join还是可以查看task 的status.
- ForkJoinPool 有一个WorkQueues数组,单一的WorkQueue,有自己的对应的pool和线程及任务队列
-
4.线程(线程与WorkQueue一一对应)的管理
- 一个新的线程的运行逻辑是什么?自己的Work Queue中queue中有任务就去执行,没有任务,要去偷.
- 这意味啥?1.run()程序中包含 是一个死循环,要时刻检测pool中有无任务执行.但是,没错,但是,这很蠢.因为很浪费.任务要是都运行完了,难道整个线程都要跑死循环.所以没有这么做.
//看下这里的run()程序中的核心部分
- 这意味啥?1.run()程序中包含 是一个死循环,要时刻检测pool中有无任务执行.但是,没错,但是,这很蠢.因为很浪费.任务要是都运行完了,难道整个线程都要跑死循环.所以没有这么做.
- 一个新的线程的运行逻辑是什么?自己的Work Queue中queue中有任务就去执行,没有任务,要去偷.
final void runWorker(WorkQueue w) {
w.growArray(); // allocate queue
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
}
}
- 这就牵扯到线程的管理了,要新增多少个线程?何时park或unpark 线程.
当然,这只能算ForkJoin的主线工作,还有很多细节很多功能,并未补充.但也说明了一些问题,有时候,代码复杂,往往在于需要多个点配合完成. 甚至会杂糅在一起,难度升级,所以,还是需要拆解工作.
接下来是主线工作的源码分析工作.细节问题不再此处拓展,下次再说.
1.从Fork 出发
//fork,新增一个task,往线程的workQueue中新增任务
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;
}
final void push(ForkJoinTask<?> task) {
ForkJoinTask<?>[] a; ForkJoinPool p;
int b = base, s = top, n;
if ((a = array) != null) {
// ignore if queue removed
int m = a.length - 1; // fenced write for task visibility
//这是找到top对应的起点位置offset
U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
//top处+1.
U.putOrderedInt(this, QTOP, s + 1);
if ((n = s - b) <= 1) {
if ((p = pool) !=