资讯专栏INFORMATION COLUMN

Java多线程进阶(四三)—— J.U.C之executors框架:Fork/Join框架(2)实现

FingerLiu / 1242人阅读

摘要:并不会为每个任务都创建工作线程,而是根据实际情况构造线程池时的参数确定是唤醒已有空闲工作线程,还是新建工作线程。

</>复制代码

  1. 本文首发于一世流云的专栏:https://segmentfault.com/blog...
一、引言

前一章——Fork/Join框架(1) 原理,我们从整体上对Fork/Join框架作了介绍。

回顾一下,Fork/Join框架的核心实现类是ForkJoinPool线程池,其它核心组件包括:ForkJoinTask(任务)、ForkJoinWorkerThread(工作线程)、WorkQueue(任务队列)。

这一章,我们将深入F/J框架的实现细节,看看ForkJoinPool线程池究竟有何特殊之处,F/J框架的整个任务调度流程又是怎样的。

二、任务调度流程

在开始之前,先来看下下面这张图:

上图包含了F/J框架的整个任务调度流程,这里先简要介绍下,以便读者在有个印象,后续的源码分析将完全按照这张图进行。

F/J框架调度任务的流程一共可以分为四大部分。

任务提交

任务提交是整个调度流程的第一步,F/J框架所调度的任务来源有两种:

①外部提交任务

所谓外部提交任务,是指通过ForkJoinPoolexecute/submit/invoke方法提交的任务,或者非工作线程(ForkJoinWorkerThread)直接调用ForkJoinTaskfork/invoke方法提交的任务:

外部提交的任务的特点就是调用线程是非工作线程。这个过程涉及以下方法:

ForkJoinPool.submit

ForkJoinPool.invoke

ForkJoinPool.execute

ForkJoinTask.fork

ForkJoinTask.invoke

ForkJoinPool.externalPush

ForkJoinPool.externalSubmit

②工作线程fork任务

所谓工作线程fork任务,是指由ForkJoinPool所维护的工作线程(ForkJoinWorkerThread)从自身任务队列中获取任务(或从其它任务队列窃取),然后执行任务。

工作线程fork任务的特点就是调用线程是工作线程。这个过程涉及以下方法:

ForkJoinTask.doExec

WorkQueue.push

创建工作线程

任务提交完成后,ForkJoinPool会根据情况创建或唤醒工作线程,以便执行任务。

ForkJoinPool并不会为每个任务都创建工作线程,而是根据实际情况(构造线程池时的参数)确定是唤醒已有空闲工作线程,还是新建工作线程。这个过程还是涉及任务队列的绑定、工作线程的注销等过程:

ForkJoinPool.signalWork

ForkJoinPool.tryAddWorker

ForkJoinPool.createWorker

ForkJoinWorkerThread.registerWorker

ForkJoinPool.deregisterWorker

任务执行

任务入队后,由工作线程开始执行,这个过程涉及任务窃取、工作线程等待等过程:

ForkJoinWorkerThread.run

ForkJoinPool.runWorker

ForkJoinPool.scan

ForkJoinPool.runTask

ForkJoinTask.doExec

ForkJoinPool.execLocalTasks

ForkJoinPool.awaitWork

任务结果获取

任务结果一般通过ForkJoinTaskjoin方法获得,其主要流程如下图:

任务结果获取的核心涉及两点:

互助窃取:ForkJoinPool.helpStealer

算力补偿:ForkJoinPool.tryCompensate

三、源码分析

通过第二部分,大致了解了F/J框架调度任务的流程,我们来看下源码实现。

任务提交

①外部提交任务

我们通过ForkJoinPoolsubmit(ForkJoinTask task)方法来看下这个过程(其它提交任务的方法内部调用几乎一样,不再赘述):

</>复制代码

  1. public ForkJoinTask submit(ForkJoinTask task) {
  2. if (task == null)
  3. throw new NullPointerException();
  4. externalPush(task);
  5. return task;
  6. }

ForkJoinPool.submit内部调用了externalPush方法:

</>复制代码

  1. final void externalPush(ForkJoinTask task) {
  2. WorkQueue[] ws;
  3. WorkQueue q;
  4. int m;
  5. int r = ThreadLocalRandom.getProbe();
  6. int rs = runState;
  7. // m & r & SQMASK必为偶数,所以通过externalPush方法提交的任务都添加到了偶数索引的任务队列中(没有绑定的工作线程)
  8. if ((ws = workQueues) != null && (m = (ws.length - 1)) >= 0 &&
  9. (q = ws[m & r & SQMASK]) != null && r != 0 && rs > 0 &&
  10. U.compareAndSwapInt(q, QLOCK, 0, 1)) {
  11. ForkJoinTask[] a;
  12. int am, n, s;
  13. if ((a = q.array) != null &&
  14. (am = a.length - 1) > (n = (s = q.top) - q.base)) {
  15. int j = ((am & s) << ASHIFT) + ABASE;
  16. U.putOrderedObject(a, j, task);
  17. U.putOrderedInt(q, QTOP, s + 1);
  18. U.putIntVolatile(q, QLOCK, 0);
  19. if (n <= 1) // 队列里只有一个任务
  20. signalWork(ws, q); // 创建或激活一个工作线程
  21. return;
  22. }
  23. U.compareAndSwapInt(q, QLOCK, 1, 0);
  24. }
  25. // 未命中任务队列时(WorkQueue == null 或 WorkQueue[i] == null),会进入该方法
  26. externalSubmit(task);
  27. }

当我们首次创建了ForkJoinPool时,任务队列数组并没有初始化,只有当首次提交任务时,才会初始化。

externalPush方法包含两部分:

根据线程随机变量、任务队列数组信息,计算命中槽(即本次提交的任务应该添加到任务队列数组中的哪个队列),如果命中且队列中任务数<1,则创建或激活一个工作线程;

否则,调用externalSubmit初始化队列,并入队。

</>复制代码

  1. /**
  2. * 完整版本的externalPush.
  3. * 处理线程池提交任务时未命中队列的情况和异常情况.
  4. */
  5. private void externalSubmit(ForkJoinTask task) {
  6. int r; // 线程相关的随机数
  7. if ((r = ThreadLocalRandom.getProbe()) == 0) {
  8. ThreadLocalRandom.localInit();
  9. r = ThreadLocalRandom.getProbe();
  10. }
  11. for (; ; ) {
  12. WorkQueue[] ws;
  13. WorkQueue q;
  14. int rs, m, k;
  15. boolean move = false;
  16. // CASE1: 线程池已关闭
  17. if ((rs = runState) < 0) {
  18. tryTerminate(false, false); // help terminate
  19. throw new RejectedExecutionException();
  20. }
  21. // CASE2: 初始化线程池
  22. else if ((rs & STARTED) == 0 || // initialize
  23. ((ws = workQueues) == null || (m = ws.length - 1) < 0)) {
  24. int ns = 0;
  25. rs = lockRunState();
  26. try {
  27. if ((rs & STARTED) == 0) {
  28. U.compareAndSwapObject(this, STEALCOUNTER, null,
  29. new AtomicLong());
  30. // 初始化工作队列数组, 数组大小必须为2的幂次
  31. int p = config & SMASK;
  32. int n = (p > 1) ? p - 1 : 1;
  33. n |= n >>> 1;
  34. n |= n >>> 2;
  35. n |= n >>> 4;
  36. n |= n >>> 8;
  37. n |= n >>> 16;
  38. n = (n + 1) << 1;
  39. workQueues = new WorkQueue[n];
  40. ns = STARTED; // 线程池状态转化为STARTED
  41. }
  42. } finally {
  43. unlockRunState(rs, (rs & ~RSLOCK) | ns);
  44. }
  45. }
  46. // CASE3: 入队任务
  47. else if ((q = ws[k = r & m & SQMASK]) != null) {
  48. if (q.qlock == 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) {
  49. ForkJoinTask[] a = q.array;
  50. int s = q.top;
  51. boolean submitted = false; // initial submission or resizing
  52. try { // locked version of push
  53. if ((a != null && a.length > s + 1 - q.base) ||
  54. (a = q.growArray()) != null) {
  55. int j = (((a.length - 1) & s) << ASHIFT) + ABASE;
  56. U.putOrderedObject(a, j, task);
  57. U.putOrderedInt(q, QTOP, s + 1);
  58. submitted = true;
  59. }
  60. } finally {
  61. U.compareAndSwapInt(q, QLOCK, 1, 0);
  62. }
  63. if (submitted) {
  64. signalWork(ws, q);
  65. return;
  66. }
  67. }
  68. move = true; // move on failure
  69. }
  70. // CASE4: 创建一个任务队列
  71. else if (((rs = runState) & RSLOCK) == 0) {
  72. q = new WorkQueue(this, null);
  73. q.hint = r;
  74. q.config = k | SHARED_QUEUE; // k为任务队列在队列数组中的索引: k == r & m & SQMASK, 在CASE3的IF判断中赋值
  75. q.scanState = INACTIVE; // 任务队列状态为INACTIVE
  76. rs = lockRunState();
  77. if (rs > 0 && (ws = workQueues) != null &&
  78. k < ws.length && ws[k] == null)
  79. ws[k] = q; // else terminated
  80. unlockRunState(rs, rs & ~RSLOCK);
  81. } else
  82. move = true; // move if busy
  83. if (move)
  84. r = ThreadLocalRandom.advanceProbe(r);
  85. }
  86. }

externalSubmit方法的逻辑很清晰,一共分为4种情况:

CASE1:线程池已经关闭,则执行终止操作,并拒绝该任务的提交;

CASE2:线程池未初始化,则进行初始化,主要就是初始化任务队列数组;

CASE3:命中了任务队列,则将任务入队,并尝试创建/唤醒一个工作线程(Worker);

CASE4:未命中任务队列,则在偶数索引处创建一个任务队列

②工作线程fork任务

工作线程fork的任务其实就是子任务,ForkJoinTask.fork方法完成。

看下ForkJoinTask.fork方法,当调用线程为工作线程时,直接添加到其自身队列中:

</>复制代码

  1. public final ForkJoinTask fork() {
  2. Thread t;
  3. if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) // 如果调用线程为【工作线程】
  4. ((ForkJoinWorkerThread) t).workQueue.push(this); // 直接添加到线程的自身队列中
  5. else
  6. ForkJoinPool.common.externalPush(this); // 外部(其它线程)提交的任务
  7. return this;
  8. }

WorkQueue.push方法,任务存入自身队列的栈顶(top):

</>复制代码

  1. final void push(ForkJoinTask task) {
  2. ForkJoinTask[] a;
  3. ForkJoinPool p;
  4. int b = base, s = top, n;
  5. if ((a = array) != null) { // ignore if queue removed
  6. int m = a.length - 1; // fenced write for task visibility
  7. U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
  8. U.putOrderedInt(this, QTOP, s + 1); // 任务存入栈顶(top+1)
  9. if ((n = s - b) <= 1) {
  10. if ((p = pool) != null)
  11. p.signalWork(p.workQueues, this); // 唤醒或创建一个工作线程
  12. } else if (n >= m)
  13. growArray(); // 扩容
  14. }
  15. }

</>复制代码

  1. 如果当前 WorkQueue 为新建的等待队列(top - base <= 1),则调用signalWork方法为当前 WorkQueue 新建或唤醒一个工作线程;
    如果 WorkQueue 中的任务数组容量过小,则调用growArray方法对其进行两倍扩容,
创建工作线程

从流程图可以看出,任务提交后,会调用signalWork方法创建或唤醒一个工作线程,该方法的核心其实就两个分支:

工作线程数不足:创建一个工作线程;

工作线程数足够:唤醒一个空闲(阻塞)的工作线程。

</>复制代码

  1. /**
  2. * 尝试创建或唤醒一个工作线程.
  3. *
  4. * @param ws 任务队列数组
  5. * @param q 当前操作的任务队列WorkQueue
  6. */
  7. final void signalWork(WorkQueue[] ws, WorkQueue q) {
  8. long c;
  9. int sp, i;
  10. WorkQueue v;
  11. Thread p;
  12. while ((c = ctl) < 0L) { // too few active
  13. // CASE1: 工作线程数不足
  14. if ((sp = (int) c) == 0) {
  15. if ((c & ADD_WORKER) != 0L)
  16. tryAddWorker(c); // 增加工作线程
  17. break;
  18. }
  19. // CASE2: 存在空闲工作线程,则唤醒
  20. if (ws == null) // unstarted/terminated
  21. break;
  22. if (ws.length <= (i = sp & SMASK)) // terminated
  23. break;
  24. if ((v = ws[i]) == null) // terminating
  25. break;
  26. int vs = (sp + SS_SEQ) & ~INACTIVE; // next scanState
  27. int d = sp - v.scanState; // screen CAS
  28. long nc = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & v.stackPred);
  29. if (d == 0 && U.compareAndSwapLong(this, CTL, c, nc)) {
  30. v.scanState = vs; // activate v
  31. if ((p = v.parker) != null)
  32. U.unpark(p);
  33. break;
  34. }
  35. if (q != null && q.base == q.top) // no more work
  36. break;
  37. }
  38. }

先来看创建工作线程的方法tryAddWorker,其实就是设置下字段值(活跃/总工作线程池数),然后调用createWorker真正创建一个工作线程:

</>复制代码

  1. private void tryAddWorker(long c) {
  2. boolean add = false;
  3. do {
  4. // 设置活跃工作线程数、总工作线程池数
  5. long nc = ((AC_MASK & (c + AC_UNIT)) |
  6. (TC_MASK & (c + TC_UNIT)));
  7. if (ctl == c) {
  8. int rs, stop; // check if terminating
  9. if ((stop = (rs = lockRunState()) & STOP) == 0)
  10. add = U.compareAndSwapLong(this, CTL, c, nc);
  11. unlockRunState(rs, rs & ~RSLOCK);
  12. if (stop != 0)
  13. break;
  14. // 创建工作线程
  15. if (add) {
  16. createWorker();
  17. break;
  18. }
  19. }
  20. } while (((c = ctl) & ADD_WORKER) != 0L && (int) c == 0);
  21. }
  22.  
  23. private boolean createWorker() {
  24. ForkJoinWorkerThreadFactory fac = factory;
  25. Throwable ex = null;
  26. ForkJoinWorkerThread wt = null;
  27. try {
  28. // 使用线程池工厂创建线程
  29. if (fac != null && (wt = fac.newThread(this)) != null) {
  30. wt.start(); // 启动线程
  31. return true;
  32. }
  33. } catch (Throwable rex) {
  34. ex = rex;
  35. }
  36. // 创建出现异常,则注销该工作线程
  37. deregisterWorker(wt, ex);
  38. return false;
  39. }

如果创建过程中出现异常,则调用deregisterWorker注销线程:

</>复制代码

  1. final void deregisterWorker(ForkJoinWorkerThread wt, Throwable ex) {
  2. WorkQueue w = null;
  3. // 1.移除workQueue
  4. if (wt != null && (w = wt.workQueue) != null) { // 获取ForkJoinWorkerThread的等待队列
  5. WorkQueue[] ws;
  6. int idx = w.config & SMASK; // 计算workQueue索引
  7. int rs = lockRunState(); // 获取runState锁和当前池运行状态
  8. if ((ws = workQueues) != null && ws.length > idx && ws[idx] == w)
  9. ws[idx] = null; // 移除workQueue
  10. unlockRunState(rs, rs & ~RSLOCK); // 解除runState锁
  11. }
  12. // 2.减少CTL数
  13. long c; // decrement counts
  14. do {
  15. } while (!U.compareAndSwapLong
  16. (this, CTL, c = ctl, ((AC_MASK & (c - AC_UNIT)) |
  17. (TC_MASK & (c - TC_UNIT)) |
  18. (SP_MASK & c))));
  19. // 3.处理被移除workQueue内部相关参数
  20. if (w != null) {
  21. w.qlock = -1; // ensure set
  22. w.transferStealCount(this);
  23. w.cancelAll(); // cancel remaining tasks
  24. }
  25. // 4.如果线程未终止,替换被移除的workQueue并唤醒内部线程
  26. for (; ; ) { // possibly replace
  27. WorkQueue[] ws;
  28. int m, sp;
  29. // 尝试终止线程池
  30. if (tryTerminate(false, false) || w == null || w.array == null ||
  31. (runState & STOP) != 0 || (ws = workQueues) == null ||
  32. (m = ws.length - 1) < 0) // already terminating
  33. break;
  34. // 唤醒被替换的线程,依赖于下一步
  35. if ((sp = (int) (c = ctl)) != 0) { // wake up replacement
  36. if (tryRelease(c, ws[sp & m], AC_UNIT))
  37. break;
  38. }
  39. // 创建工作线程替换
  40. else if (ex != null && (c & ADD_WORKER) != 0L) {
  41. tryAddWorker(c); // create replacement
  42. break;
  43. } else // don"t need replacement
  44. break;
  45. }
  46. // 5.处理异常
  47. if (ex == null) // help clean on way out
  48. ForkJoinTask.helpExpungeStaleExceptions();
  49. else // rethrow
  50. ForkJoinTask.rethrow(ex);
  51. }

</>复制代码

  1. deregisterWorker方法用于工作线程运行完毕之后终止线程或处理工作线程异常,主要就是清除已关闭的工作线程或回滚创建线程之前的操作,并把传入的异常抛给 ForkJoinTask 来处理。

工作线程在构造的过程中,会保存线程池信息和与自己绑定的任务队列信息。它通过ForkJoinPool.registerWorker方法将自己注册到线程池中:

</>复制代码

  1. protected ForkJoinWorkerThread(ForkJoinPool pool) {
  2. // Use a placeholder until a useful name can be set in registerWorker
  3. super("aForkJoinWorkerThread");
  4. this.pool = pool;
  5. this.workQueue = pool.registerWorker(this);
  6. }

</>复制代码

  1. final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
  2. UncaughtExceptionHandler handler;
  3. wt.setDaemon(true); // configure thread
  4. if ((handler = ueh) != null)
  5. wt.setUncaughtExceptionHandler(handler);
  6. // 创建一个工作队列, 并于该工作线程绑定
  7. WorkQueue w = new WorkQueue(this, wt);
  8. int i = 0; // 记录队列在任务队列数组中的索引, 始终为奇数
  9. int mode = config & MODE_MASK;
  10. int rs = lockRunState();
  11. try {
  12. WorkQueue[] ws;
  13. int n;
  14. if ((ws = workQueues) != null && (n = ws.length) > 0) {
  15. int s = indexSeed += SEED_INCREMENT; // unlikely to collide
  16. int m = n - 1;
  17. i = ((s << 1) | 1) & m; // 经计算后, i为奇数
  18. if (ws[i] != null) { // 槽冲突, 即WorkQueue[i]位置已经有了任务队列
  19. // 重新计算索引i
  20. int probes = 0; // step by approx half n
  21. int step = (n <= 4) ? 2 : ((n >>> 1) & EVENMASK) + 2;
  22. while (ws[i = (i + step) & m] != null) {
  23. if (++probes >= n) {
  24. workQueues = ws = Arrays.copyOf(ws, n <<= 1);
  25. m = n - 1;
  26. probes = 0;
  27. }
  28. }
  29. }
  30. // 设置队列状态为SCANNING
  31. w.hint = s; // use as random seed
  32. w.config = i | mode;
  33. w.scanState = i; // publication fence
  34. ws[i] = w;
  35. }
  36. } finally {
  37. unlockRunState(rs, rs & ~RSLOCK);
  38. }
  39. wt.setName(workerNamePrefix.concat(Integer.toString(i >>> 1)));
  40. return w;
  41. }

前文讲过,工作线程(Worker)自身的任务队列,其数组下标始终是奇数,registerWorker方法的主要作用就是在任务队列数组WorkQueue[]找到一个空的奇数位,然后在该位置创建WorkQueue

至此,线程池的任务提交工作和工作线程创建工作就全部完成了,接下来开始工作线程的执行。

任务执行

ForkJoinWorkerThread启动后,会执行它的run方法,该方法内部调用了ForkJoinPool.runWorker方法来执行任务:

</>复制代码

  1. public void run() {
  2. if (workQueue.array == null) { // only run once
  3. Throwable exception = null;
  4. try {
  5. onStart(); // 钩子方法
  6. pool.runWorker(workQueue);
  7. } catch (Throwable ex) {
  8. exception = ex;
  9. } finally {
  10. try {
  11. onTermination(exception);
  12. } catch (Throwable ex) {
  13. if (exception == null)
  14. exception = ex;
  15. } finally {
  16. pool.deregisterWorker(this, exception);
  17. }
  18. }
  19. }
  20. }

runWorker方法的核心流程如下:

scan:尝试获取一个任务;

runTask:执行取得的任务;

awaitWork:没有任务则阻塞。

</>复制代码

  1. final void runWorker(WorkQueue w) {
  2. w.growArray(); // 初始化任务队列
  3. int seed = w.hint; // initially holds randomization hint
  4. int r = (seed == 0) ? 1 : seed; // avoid 0 for xorShift
  5. for (ForkJoinTask t; ; ) {
  6. // CASE1: 尝试获取一个任务
  7. if ((t = scan(w, r)) != null)
  8. w.runTask(t); // 获取成功, 执行任务
  9. // CASE2: 获取失败, 阻塞等待任务入队
  10. else if (!awaitWork(w, r)) // 等待失败, 跳出该方法后, 工作线程会被注销
  11. break;
  12. r ^= r << 13;
  13. r ^= r >>> 17;
  14. r ^= r << 5; // xorshift
  15. }
  16. }

</>复制代码

  1. 注意:如果awaitWork返回false,等不到任务,则跳出runWorker的循环,回到run中执行finally,最后调用deregisterWorker注销工作线程。

任务窃取——scan

</>复制代码

  1. private ForkJoinTask scan(WorkQueue w, int r) {
  2. WorkQueue[] ws;
  3. int m;
  4. if ((ws = workQueues) != null && (m = ws.length - 1) > 0 && w != null) {
  5. int ss = w.scanState; // initially non-negative
  6. for (int origin = r & m, k = origin, oldSum = 0, checkSum = 0; ; ) {
  7. WorkQueue q;
  8. ForkJoinTask[] a;
  9. ForkJoinTask t;
  10. int b, n;
  11. long c;
  12. // 根据随机数r定位一个任务队列
  13. if ((q = ws[k]) != null) { // 获取workQueue
  14. if ((n = (b = q.base) - q.top) < 0 &&
  15. (a = q.array) != null) { // non-empty
  16. long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
  17. if ((t = ((ForkJoinTask)
  18. U.getObjectVolatile(a, i))) != null && // 取base位置任务
  19. q.base == b) {
  20. // 成功获取到任务
  21. if (ss >= 0) {
  22. if (U.compareAndSwapObject(a, i, t, null)) {
  23. q.base = b + 1; // 更新base位
  24. if (n < -1)
  25. signalWork(ws, q); // 创建或唤醒工作线程来运行任务
  26. return t;
  27. }
  28. } else if (oldSum == 0 && // try to activate
  29. w.scanState < 0)
  30. tryRelease(c = ctl, ws[m & (int) c], AC_UNIT); // 唤醒栈顶工作线程
  31. }
  32. // base位置任务为空或base位置偏移,随机移位重新扫描
  33. if (ss < 0) // refresh
  34. ss = w.scanState;
  35. r ^= r << 1;
  36. r ^= r >>> 3;
  37. r ^= r << 10;
  38. origin = k = r & m; // move and rescan
  39. oldSum = checkSum = 0;
  40. continue;
  41. }
  42. checkSum += b;
  43. }
  44. if ((k = (k + 1) & m) == origin) { // continue until stable
  45. // 运行到这里说明已经扫描了全部的 workQueues,但并未扫描到任务
  46. if ((ss >= 0 || (ss == (ss = w.scanState))) &&
  47. oldSum == (oldSum = checkSum)) {
  48. if (ss < 0 || w.qlock < 0) // already inactive
  49. break;
  50. // 对当前WorkQueue进行灭活操作
  51. int ns = ss | INACTIVE; // try to inactivate
  52. long nc = ((SP_MASK & ns) |
  53. (UC_MASK & ((c = ctl) - AC_UNIT)));
  54. w.stackPred = (int) c; // hold prev stack top
  55. U.putInt(w, QSCANSTATE, ns);
  56. if (U.compareAndSwapLong(this, CTL, c, nc))
  57. ss = ns;
  58. else
  59. w.scanState = ss; // back out
  60. }
  61. checkSum = 0;
  62. }
  63. }
  64. }
  65. return null;
  66. }

扫描并尝试偷取一个任务。随机选择一个WorkQueue,获取base位的 ForkJoinTask,成功取到后更新base位并返回任务;如果取到的 WorkQueue 中任务数大于1,则调用signalWork创建或唤醒其他工作线程。

阻塞等待——awaitWork

如果scan方法未扫描到任务,会调用awaitWork等待获取任务:

</>复制代码

  1. private boolean awaitWork(WorkQueue w, int r) {
  2. if (w == null || w.qlock < 0) // w is terminating
  3. return false;
  4. for (int pred = w.stackPred, spins = SPINS, ss; ; ) {
  5. if ((ss = w.scanState) >= 0) // 正在扫描,跳出循环
  6. break;
  7. else if (spins > 0) {
  8. r ^= r << 6;
  9. r ^= r >>> 21;
  10. r ^= r << 7;
  11. if (r >= 0 && --spins == 0) { // randomize spins
  12. WorkQueue v;
  13. WorkQueue[] ws;
  14. int s, j;
  15. AtomicLong sc;
  16. if (pred != 0 && (ws = workQueues) != null &&
  17. (j = pred & SMASK) < ws.length &&
  18. (v = ws[j]) != null && // see if pred parking
  19. (v.parker == null || v.scanState >= 0))
  20. spins = SPINS; // continue spinning
  21. }
  22. } else if (w.qlock < 0) // 当前workQueue已经终止,返回false recheck after spins
  23. return false;
  24. else if (!Thread.interrupted()) { // 判断线程是否被中断,并清除中断状态
  25. long c, prevctl, parkTime, deadline;
  26. int ac = (int) ((c = ctl) >> AC_SHIFT) + (config & SMASK); // 活跃线程数
  27. if ((ac <= 0 && tryTerminate(false, false)) || // 无active线程,尝试终止
  28. (runState & STOP) != 0) // pool terminating
  29. return false;
  30. if (ac <= 0 && ss == (int) c) { // is last waiter
  31. // 计算活跃线程数(高32位)并更新为下一个栈顶的scanState(低32位)
  32. prevctl = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & pred);
  33. int t = (short) (c >>> TC_SHIFT); // shrink excess spares
  34. if (t > 2 && U.compareAndSwapLong(this, CTL, c, prevctl))//总线程过量
  35. return false; // else use timed wait
  36. // 计算空闲超时时间
  37. parkTime = IDLE_TIMEOUT * ((t >= 0) ? 1 : 1 - t);
  38. deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP;
  39. } else
  40. prevctl = parkTime = deadline = 0L;
  41. Thread wt = Thread.currentThread();
  42. U.putObject(wt, PARKBLOCKER, this); // emulate LockSupport
  43. w.parker = wt; // 设置parker,准备阻塞
  44. if (w.scanState < 0 && ctl == c) // recheck before park
  45. U.park(false, parkTime); // 阻塞指定的时间
  46. U.putOrderedObject(w, QPARKER, null);
  47. U.putObject(wt, PARKBLOCKER, null);
  48. if (w.scanState >= 0) // 正在扫描,说明等到任务,跳出循环
  49. break;
  50. if (parkTime != 0L && ctl == c &&
  51. deadline - System.nanoTime() <= 0L &&
  52. U.compareAndSwapLong(this, CTL, c, prevctl)) // 未等到任务,更新ctl,返回false
  53. return false; // shrink pool
  54. }
  55. }
  56. return true;
  57. }

任务执行——runTask

窃取到任务后,调用WorkQueue.runTask方法执行任务:

</>复制代码

  1. final void runTask(ForkJoinTask task) {
  2. if (task != null) {
  3. scanState &= ~SCANNING; // mark as busy
  4. (currentSteal = task).doExec(); // 更新currentSteal并执行任务
  5. U.putOrderedObject(this, QCURRENTSTEAL, null); // release for GC
  6. execLocalTasks(); // 依次执行本地任务
  7. ForkJoinWorkerThread thread = owner;
  8. if (++nsteals < 0) // collect on overflow
  9. transferStealCount(pool); // 增加偷取任务数
  10. scanState |= SCANNING;
  11. if (thread != null)
  12. thread.afterTopLevelExec(); // 执行钩子函数
  13. }
  14. }

1.首先调用FutureTask.deExec()执行任务,其内部会调用FutureTask.exec()方法,该方法为抽象方法,由子类实现。

子类实现该方法时,一般会进行fork,导致生成子任务,并最终添加到调用线程自身地任务队列中:

</>复制代码

  1. final int doExec() {
  2. int s;
  3. boolean completed;
  4. if ((s = status) >= 0) {
  5. try {
  6. completed = exec(); // exec为抽象方法, 由子类实现
  7. } catch (Throwable rex) {
  8. return setExceptionalCompletion(rex);
  9. }
  10. if (completed)
  11. s = setCompletion(NORMAL);
  12. }
  13. return s;
  14. }

2.除了执行窃取到的任务,工作线程还会执行自己队列中的任务,即WorkQueue.execLocalTasks方法:

</>复制代码

  1. final void execLocalTasks() {
  2. int b = base, m, s;
  3. ForkJoinTask[] a = array;
  4. if (b - (s = top - 1) <= 0 && a != null &&
  5. (m = a.length - 1) >= 0) {
  6. if ((config & FIFO_QUEUE) == 0) { // LIFO, 从top -> base 遍历执行任务
  7. for (ForkJoinTask t; ; ) {
  8. if ((t = (ForkJoinTask) U.getAndSetObject
  9. (a, ((m & s) << ASHIFT) + ABASE, null)) == null)
  10. break;
  11. U.putOrderedInt(this, QTOP, s);
  12. t.doExec();
  13. if (base - (s = top - 1) > 0)
  14. break;
  15. }
  16. } else // FIFO, 从base -> top 遍历执行任务
  17. pollAndExecAll();
  18. }
  19. }

</>复制代码

  1. 构建线程池时的asyncMode参数,决定了工作线程执行自身队列中的任务的方式。如果 asyncMode == true,则以FIFO的方式执行任务;否则,以LIFO的方式执行任务。
任务结果获取

ForkJoinTask.join()可以用来获取任务的执行结果。join方法的执行逻辑如下:

</>复制代码

  1. public final V join() {
  2. int s;
  3. if ((s = doJoin() & DONE_MASK) != NORMAL)
  4. reportException(s);
  5. return getRawResult();
  6. }

可以看到,内部先调用doJoin方法:

</>复制代码

  1. private int doJoin() {
  2. int s;
  3. Thread t;
  4. ForkJoinWorkerThread wt;
  5. ForkJoinPool.WorkQueue w;
  6. return (s = status) < 0 ? s :
  7. ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
  8. (w = (wt = (ForkJoinWorkerThread) t).workQueue).tryUnpush(this) && (s = doExec()) < 0 ? s :
  9. wt.pool.awaitJoin(w, this, 0L) :
  10. externalAwaitDone();
  11. }

 
doJoin方法会判断调用线程是否是工作线程:

1.如果是非工作线程调用的join,则最终调用externalAwaitDone()阻塞等待任务的完成。

2.如果是工作线程调用的join,则存在以下情况:

如果需要join的任务已经完成,直接返回运行结果;

如果需要join的任务刚刚好是当前线程所拥有的队列的top位置,则立即执行它。

如果该任务不在top位置,则调用awaitJoin方法等待

关键看下ForkJoinPool.awaitJoin等待过程中发生了什么:

</>复制代码

  1. final int awaitJoin(WorkQueue w, ForkJoinTask task, long deadline) {
  2. int s = 0;
  3. if (task != null && w != null) {
  4. ForkJoinTask prevJoin = w.currentJoin; // 获取给定Worker的join任务
  5. U.putOrderedObject(w, QCURRENTJOIN, task); // 把currentJoin替换为给定任务
  6. // 判断是否为CountedCompleter类型的任务
  7. CountedCompleter cc = (task instanceof CountedCompleter) ?
  8. (CountedCompleter) task : null;
  9. for (; ; ) {
  10. if ((s = task.status) < 0) // 已经完成|取消|异常 跳出循环
  11. break;
  12. if (cc != null) // CountedCompleter任务由helpComplete来完成join
  13. helpComplete(w, cc, 0);
  14. else if (w.base == w.top || w.tryRemoveAndExec(task)) //尝试执行
  15. helpStealer(w, task); // 队列为空或执行失败,任务可能被偷,帮助偷取者执行该任务
  16. if ((s = task.status) < 0) // 已经完成|取消|异常,跳出循环
  17. break;
  18. // 计算任务等待时间
  19. long ms, ns;
  20. if (deadline == 0L)
  21. ms = 0L;
  22. else if ((ns = deadline - System.nanoTime()) <= 0L)
  23. break;
  24. else if ((ms = TimeUnit.NANOSECONDS.toMillis(ns)) <= 0L)
  25. ms = 1L;
  26. if (tryCompensate(w)) { // 执行补偿操作
  27. task.internalWait(ms); // 补偿执行成功,任务等待指定时间
  28. U.getAndAddLong(this, CTL, AC_UNIT); // 更新活跃线程数
  29. }
  30. }
  31. U.putOrderedObject(w, QCURRENTJOIN, prevJoin); // 循环结束,替换为原来的join任务
  32. }
  33. return s;
  34. }

ForkJoinPool.awaitJoin方法中有三个重要方法:

tryRemoveAndExec

helpStealer

tryCompensate

这里说下这三个方法的主要作用,不贴代码了:

tryRemoveAndExec:

当工作线程正在等待join的任务时,它会从top位开始自旋向下查找该任务:

如果找到则移除并执行它;

如果找不到,说明说明任务可能被偷,则调用helpStealer方法反过来帮助偷取者执行它自己的任务。

helpStealer:

先定位的偷取者的任务队列;

从偷取者的base索引开始,每次偷取一个任务执行。

tryCompensate:

tryCompensate主要用来补偿工作线程因为阻塞而导致的算力损失,当工作线程自身的队列不为空,且还有其它空闲工作线程时,如果自己阻塞了,则在此之前会唤醒一个工作线程。

四、总结

本章和上一章——Fork/Join框架(1) 原理,从思想、使用、实现等方面较完整地分析了Fork/Join框架,Fork/Join框架的使用需要根据实际情况划分子任务的大小。

理解F/J框架需要先从整体上了解框架调度任务的流程(参考本章开头的调度图),可以自己通过示例模拟一个任务的调度过程,然后根据实际运用过程中遇到的问题,再去调试及在相应的源码中查看实现原理。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71970.html

相关文章

  • Java线程进阶四三)—— J.U.Cexecutors框架Fork/Join框架(1) 原

    摘要:同时,它会通过的方法将自己注册到线程池中。线程池中的每个工作线程都有一个自己的任务队列,工作线程优先处理自身队列中的任务或顺序,由线程池构造时的参数决定,自身队列为空时,以的顺序随机窃取其它队列中的任务。 showImg(https://segmentfault.com/img/bVbizJb?w=1802&h=762); 本文首发于一世流云的专栏:https://segmentfau...

    cooxer 评论0 收藏0
  • Java线程进阶(一)—— J.U.C并发包概述

    摘要:整个包,按照功能可以大致划分如下锁框架原子类框架同步器框架集合框架执行器框架本系列将按上述顺序分析,分析所基于的源码为。后,根据一系列常见的多线程设计模式,设计了并发包,其中包下提供了一系列基础的锁工具,用以对等进行补充增强。 showImg(https://segmentfault.com/img/remote/1460000016012623); 本文首发于一世流云专栏:https...

    anonymoussf 评论0 收藏0
  • Java线程进阶(三九)—— J.U.Cexecutors框架executors框架概述

    摘要:注意线程与本地操作系统的线程是一一映射的。固定线程数的线程池提供了两种创建具有固定线程数的的方法,固定线程池在初始化时确定其中的线程总数,运行过程中会始终维持线程数量不变。 showImg(https://segmentfault.com/img/bVbhK58?w=1920&h=1080); 本文首发于一世流云专栏:https://segmentfault.com/blog... ...

    wdzgege 评论0 收藏0
  • Java线程进阶(四二)—— J.U.Cexecutors框架:Future模式

    摘要:本文首发于一世流云的专栏一模式简介模式是多线程设计模式中的一种常见模式,它的主要作用就是异步地执行任务,并在需要的时候获取结果。二中的模式在多线程基础之模式中,我们曾经给出过模式的通用类关系图。 showImg(https://segmentfault.com/img/bVbiwcx?w=1000&h=667); 本文首发于一世流云的专栏:https://segmentfault.co...

    marek 评论0 收藏0
  • 聊聊面试中关于并发问题的应对方案

    摘要:这里呢,我直接给出高并发场景通常都会考虑的一些解决思路和手段结尾如何有效的准备面试中并发类问题,我已经给出我的理解。 showImg(https://segmentfault.com/img/bV7Viy?w=550&h=405); 主题 又到面试季了,从群里,看到许多同学分享了自己的面试题目,我也抽空在网上搜索了一些许多公司使用的面试题,目前校招和社招的面试题基本都集中在几个大方向上...

    xzavier 评论0 收藏0

发表评论

0条评论

FingerLiu

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<