ForkJoinPool源码解析(二)-- 任务执行
参考自 https://blog.csdn.net/lcbushihaha/article/details/104449454
本文分析线程的执行
ForkJoinWorkerThread.run
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); } } } }
final void runWorker(WorkQueue w)
final void runWorker(WorkQueue w) { w.growArray(); // allocate queue new一个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 } }
ForkJoinPool.scan
private ForkJoinTask<?> scan(WorkQueue w, int r) { //m:任务队列数组的长度-1 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; //k是一个随机的数,如果不等于null则 if ((q = ws[k]) != null) { //队列中存在任务 if ((n = (b = q.base) - q.top) < 0 && (a = q.array) != null) { // non-empty //计算base的偏移量 long i = (((a.length - 1) & b) << ASHIFT) + ABASE; //取出base下标的任务 if ((t = ((ForkJoinTask<?>) U.getObjectVolatile(a, i))) != null && q.base == b) { //ss初始时为非负数,(队列在队列数组中的下标) if (ss >= 0) { //将base下标处的任务置为null 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) //如果scanState为负数且oldsum为0 //scanState什么时候会变为负数,在队列失活的时候 //尝试去激活ctl低32位的队列 tryRelease(c = ctl, ws[m & (int)c], AC_UNIT); } if (ss < 0) // refresh //刷新ss的值,避免被其他线程修改了未更新 ss = w.scanState; //发生竞争随机移动 r ^= r << 1; r ^= r >>> 3; r ^= r << 10; origin = k = r & m; // move and rescan oldSum = checkSum = 0; //继续扫描 continue; } //如果数组为空,则会checkSum的值会加上队列q的base checkSum += b; } //能到这里说明ws[k]为null或为空或出现了竞争, // k线性加1,直到发现已经从一个origin转满了一圈或n圈. if ((k = (k + 1) & m) == origin) { // continue until stable 条件:scanState表示活跃,或者满足当前线程工作队列w的ss未改变, // oldSum依旧等于最新的checkSum(校验和未改变) if ((ss >= 0 || (ss == (ss = w.scanState))) && oldSum == (oldSum = checkSum)) { //能进入这里说明w[k]不为null,而是队列为空,所以需要灭活 //ss小于0说明队列被灭活了,队列的qlock小于0说明已经终止了 if (ss < 0 || w.qlock < 0) // already inactive break; int ns = ss | INACTIVE; // try to inactivate //将低三十二位替换成scanState 活跃线程减1 long nc = ((SP_MASK & ns) | (UC_MASK & ((c = ctl) - AC_UNIT))); //将先前的栈顶替换存放在新栈顶的stackPred上 w.stackPred = (int)c; // hold prev stack top //将w的scanState设置为新的值,和ctl的低三十二位一样 U.putInt(w, QSCANSTATE, ns); if (U.compareAndSwapLong(this, CTL, c, nc)) //CAS成功,将ss更新为新值 ss = ns; else //CAS失败,还原 w.scanState = ss; // back out } checkSum = 0; } } } return null; }
扫描整个队列连续出现两次扫描的checkSum的值相同,说明所有的队列都是空的了 需要去灭活当前的队列。因为两次checkSum的值相同说明两次都便利了所有的队列的base 也就是都是线性的增加k的值,如果有的队列有元素发生竞争失败了会随机移动下标, 很大概率不会形成两次一样checkSum的。
如果scan没有扫描到任务会将这个队列失活,并放入将队列的scanState字段方法ctl的低32位,替换原来的值并将原来的值放入当前队列的stackPred字段构成一个栈。scan没有扫描到任务返回后,runWork方法会调用awaitWork方法阻塞线程。
WorkQueue.final void runTask(ForkJoinTask<?> task)
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(); } }
这里执行 doExec() 会继续调用 ForkJoinTask.exec()。但是在实际开发中,我们一般不会直接继承ForkJoinTask。而是继承他的两个子类
RecursiveTask 和 RecursiveAction 。前者有返回值后者没有返回值。当继承了RecursiveTask只需要实现 protected abstract V compute();
而我们再实现一个RecursiveTask 通常是这么写的
private static class SumTask extends RecursiveTask<Long> { private long[] numbers; private int from; private int to; public SumTask(long[] numbers, int from, int to) { this.numbers = numbers; this.from = from; this.to = to; } //此方法为ForkJoin的核心方法:对任务进行拆分 拆分的好坏决定了效率的高低 @Override protected Long compute() { // 当需要计算的数字个数小于6时,直接采用for loop方式计算结果 if (to - from < 6) { long total = 0; for (int i = from; i <= to; i++) { total += numbers[i]; } return total; } else { // 否则,把任务一分为二,递归拆分(注意此处有递归)到底拆分成多少分 需要根据具体情况而定 int middle = (from + to) / 2; SumTask taskLeft = new SumTask(numbers, from, middle); SumTask taskRight = new SumTask(numbers, middle + 1, to); taskLeft.fork(); taskRight.fork(); return taskLeft.join() + taskRight.join(); } } }
再来看
final void runTask(ForkJoinTask<?> task) { if (task != null) { scanState &= ~SCANNING; // mark as busy (currentSteal = task).doExec(); //会执行fork方法,把子任务丢进自己的任务队列中 U.putOrderedObject(this, QCURRENTSTEAL, null); // release for GC execLocalTasks(); //这个方法会继续执行每个子任务的compute方法继续拆解子任务,不断地fork join直到满足compute的条件不再fork
//实际跟代码过程中上面的方法进不来,不会满足条件的
ForkJoinWorkerThread thread = owner; if (++nsteals < 0) // collect on overflow transferStealCount(pool); scanState |= SCANNING; if (thread != null) thread.afterTopLevelExec(); } }
本文就到这里,join的逻辑留到下一篇,同时join中也实现了任务盗取功能
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(); } }