作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬
学习必须往深处挖,挖的越深,基础越扎实!
阶段1、深入多线程
阶段2、深入多线程设计模式
阶段3、深入juc源码解析
码哥源码部分
码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】
码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】
码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】
码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】
打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】
一、引言
前一章——Fork/Join框架(1) 原理,我们从整体上对Fork/Join框架作了介绍。
回顾一下,Fork/Join框架的核心实现类是 ForkJoinPool 线程池,其它核心组件包括:
ForkJoinTask (任务)、 ForkJoinWorkerThread (工作线程)、 WorkQueue (任务队列)。
这一章,我们将深入F/J框架的实现细节,看看ForkJoinPool线程池究竟有何特殊之处,F/J框架的整个任务调度流程又是怎样的。
二、任务调度流程
在开始之前,先来看下下面这张图:
上图包含了F/J框架的整个任务调度流程,这里先简要介绍下,以便读者在有个印象,后续的源码分析将完全按照这张图进行。
F/J框架调度任务的流程一共可以分为四大部分:
任务提交
任务提交是整个调度流程的第一步,F/J框架所调度的任务来源有两种:
①外部提交任务
所谓外部提交任务,是指通过 ForkJoinPool 的execute
/submit
/invoke
方法提交的任务,或者非工作线程( ForkJoinWorkerThread )直接调用 ForkJoinTask 的fork
/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
任务结果获取
任务结果一般通过 ForkJoinTask 的join
方法获得,其主要流程如下图:
任务结果获取的核心涉及两点:
- 互助窃取:
ForkJoinPool.helpStealer
- 算力补偿:
ForkJoinPool.tryCompensate
三、源码分析
通过第二部分,大致了解了F/J框架调度任务的流程,我们来看下源码实现。
任务提交
①外部提交任务
我们通过 ForkJoinPool 的submit(ForkJoinTask<T> task)
方法来看下这个过程(其它提交任务的方法内部调用几乎一样,不再赘述):
public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) {
if (task == null)
throw new NullPointerException();
externalPush(task);
return task;
}
ForkJoinPool.submit
内部调用了externalPush
方法:
final void externalPush(ForkJoinTask<?> task) {
WorkQueue[] ws;
WorkQueue q;
int m;
int r = ThreadLocalRandom.getProbe();
int rs = runState;
// m & r & SQMASK必为偶数,所以通过externalPush方法提交的任务都添加到了偶数索引的任务队列中(没有绑定的工作线程)
if ((ws = workQueues) != null && (m = (ws.length - 1)) >= 0 &&
(q = ws[m & r & SQMASK]) != null && r != 0 && rs > 0 &&
U.compareAndSwapInt(q, QLOCK, 0, 1)) {
ForkJoinTask<?>[] a;
int am, n, s;
if ((a = q.array) != null &&
(am = a.length - 1) > (n = (s = q.top) - q.base)) {
int j = ((am & s) << ASHIFT) + ABASE;
U.putOrderedObject(a, j, task);
U.putOrderedInt(q, QTOP, s + 1);
U.putIntVolatile(q, QLOCK, 0);
if (n <= 1) // 队列里只有一个任务
signalWork(ws, q); // 创建或激活一个工作线程