深入ForkJoinPool源码
概念
ForkJoin框架将一个大问题分解成若干个相互独立且容易解决的小问题,为了让这些小问题能够并行执行,同时减少线程间对资源的竞争,将这些小问题分别放入到不同的队列中,并为每个队列创建一个线程来执行队列中的小问题,这个过程就叫做Fork,中文称作分支,当所有的小问题都解决完之后,需要将每个结果进行汇总合并最终得出大问题的结果,这个过程叫做Join,中文称作合并,这就好比生活中建设大厦一般,值得注意的是在执行过程应用了一种叫做工作窃取的算法,如此思想在算法界中称作分治,分而治之。
工作窃取:一个线程对应一个队列,每个队列中有多个小任务,当其中一个线程执行完队列中的任务时,为了充分利用线程资源,该线程将从任意一个队列中窃取任务来执行,而该队列对应的线程也会从该队列中获取任务,所以为了减少自身队列对应的线程与窃取线程之间不必要的竞争,通常使用双端队列,约定自身线程从队列的尾部开始获取(LIFO),窃取线程从队列的头部开始获取(FIFO),当然了,有一种情况下还是避免不了竞争,队列中只有一个任务时,这个时候就需要加锁或CAS进行控制。
ForkJoinPool与ExecutorService的主要区别便是工作窃取,其应用场景也不同,分而治之的思想无疑减少了程序的运行时间,提供了效率,但同时占用了更多的资源,以下提供图片帮助理解:

目前对ForkJoin框架有一定程度上的理解了,之前写的文章都是通过先阅读注释开始,但是ForkJoinPool类的注释实际上并无很重要的关键信息,有兴趣的读者可以尝试去阅读Doug Lea关于ForkJoin框架的理论或者ForkJoinPool内部的注释,接下来进入到源码环节。
数据结构
//直接继承AbstractExecutorService,说明它与ThreadPoolExecutor是并列关系,两者并无依赖关系,两个线程池所应用的场景不同
public class ForkJoinPool extends AbstractExecutorService {
/**
* 通过 | 计算来保存线程池的并行数(parallelism)与模式(asyncMode)信息,低16位表示并行数,高16位表示模式
*
* 0000 0000 0000 0001 0000 0000 0000 0001
* | 高16位 || 低16位 |
*
* 并行数:简单来说就是工作线程的个数,相当于在指明可以创建多少个工作线程,一个工作线程对应一个队列,而队列中的任务可以被多个线程并行执行(工作窃取)
*
* 模式:该模式跟同步异步没有任何的关系,我们都知道一个线程对应一个队列,而队列采用的是双端队列,那么当线程要处理自身队列中的任务时,就该考虑任务是要以先进先出(FIFO)的顺序还是以先进后出(LIFO)的顺序呢?
* 所以该模式旨在配置任务以哪种顺序出队列,若大于0(1 << 16),表示以先进先出的顺序出队列,该模式下的线程池更适合处理基于事件类型的异步任务;默认情况下以先进后出(0)的顺序出队列;
* 当线程以先进先出的顺序处理自身队列中的任务时,同时其他线程会去窃取其任务,也就是说多个线程去抢夺任务,资源竞争很是激烈,降低了任务的处理速度
*
* 注意:队列中也有该属性名称,但表示的含义并不同
*/
final int config;
/**
* 该属性是long类型,故而它共有64位,很有意思,它将64位分成4部分(从1开始数位数,每部分都有16位)
* 第一部分AC(49位 - 64位):活跃的工作线程数减去并行数(至于为什么要减去一会下面说), 该结果小于0时表明没有足够的活跃线程数
* 第二部分TC(33位 - 48位):所有的工作线程数减去并行数, 该结果小于0时表明没有足够的线程数
* 第三部分SS(17位- 32位):空闲队列的版本计数(空闲->活跃,活跃->空闲)与状态(空闲/活跃)
* 第四部分ID(1位- 16位):空闲队列在队列数组中的索引
*
* 当 1-32位,通常使用sp = (int)c来表示,当sp = 0 表示没有空闲线程 当sp > 0 则表示有空闲线程
*
* 1. 解释下为什么要减去并行数?
* 首先在一开始时我们指定了并行数,也就是表明将会创建指定个数的工作线程,所以一开始ctl#AC的值应该是负的并行数(这个时候还未添加任务),该结果可参考 (np << AC_SHIFT) & AC_MASK) 计算就可知道
* 如果只是单纯地表示工作线程数,那么一开始也应该是对应到并行数,可是咱们还未开始添加任务就已经有活跃的工作线程数了是不是有点问题???
*
* 将其分成4个部分是为了以原子性的方式去添加/禁用队列,控制入队与出队,重新激活工作线程等,所以它相当于线程池的控制中心
*/
volatile long ctl;
/**
* 线程池的运行状态
* RSLOCK:线程池处于锁定状态,当有多个线程同时更新队列数组时只允许一个线程能够执行操作,否则将出现问题
* STARTED:线程池处于已启动状态
* RSIGNAL:标识线程池需要去唤醒其他线程
* STOP:线程池处于已停止,此时还有执行中的任务,不过队列已清空
* TERMINATED:线程池处于已终止
* SHUTDOWN:线程池处于已关闭,此时还未执行任何清空操作
*/
volatile int runState;
/**
* 用于生成工作线程的索引
*/
int indexSeed;
/**
* 队列数组,存储所有队列用来获取自身与窃取其他队列的任务
*/
volatile WorkQueue[] workQueues;
/**
* 工作线程的工厂类,自定义创建工作线程
*/
final ForkJoinWorkerThreadFactory factory;
/**
* 工作线程的默认工厂
*/
public static final ForkJoinWorkerThreadFactory defaultForkJoinWorkerThreadFactory = new DefaultForkJoinWorkerThreadFactory();
/**
* 异常未处理的自定义处理器,用于处理异常信息
*/
final UncaughtExceptionHandler ueh;
/**
* 工作线程的前缀名称
*/
final String workerNamePrefix;
/**
* 有两个作用:
* 1. 当多个线程同时竞争线程池的使用权时,未抢到的线程可能被阻塞,而发生阻塞时需要先获取到锁,故而用于锁
* 2. 表明当前已窃取的任务数量,要获取总共窃取的任务数量需要加上奇数索引下每个队列的nsteals值,参考getStealCount
*/
volatile AtomicLong stealCounter;
/**
* 常量池,通过 & 计算并行数(工作线程的个数)/ 在队列数组中的索引值
*/
static final int SMASK = 0xffff;
/**
* 常量值,限制工作线程的最大个数
*/
static final int MAX_CAP = 0x7fff;
/**
* 常量值,通过 & 使其结果呈现偶数
* 通常情况下我们已经有一个队列数组了,那么只要将创建好的队列放入到数组中即可,但是它在这里将任务提交分两种情况,故而需要将队列分成两种情况来分析:
* 1. 调用ForkJoinTask#fork的任务会被放到处于奇数索引的队列中,此队列被称作内部队列
* 2. 利用线程池直接提交任务,比如execute/submit,这些任务会被放到处于偶数索引的队列中,此队列被称作共享队列
* 多说两句,之所以称作共享队列,其实是有原因的,这些队列的任务并不会用执行线程去执行,而是会被多个线程窃取,一个一个地被窃取,表明谁都可以来拿这队列里的任务去执行
* 所以才叫做共享队列,纯属个人理解哈
*
* 之所以这么做是为了简化处理与加速任务扫描窃取,要不然就要用两个队列数组分别存储,毕竟ForkJoinTask#fork的任务后续还需要进行join,那就必须要进行筛选
*/
static final int EVENMASK = 0xfffe;
/**
* 常量值,通过 & 计算偶数索引,从0-126之间的所有偶数,共有64个
*/
static final int SQMASK = 0x007e;
/**
* 常量值,通过 & 与scanState计算队列是否处于运行中,scanState为奇数时表明队列在使用中,奇数 & 1 还是奇数,偶数 & 1还是偶数
*/
static final int SCANNING = 1;
/**
* 常量值,标识队列处于空闲状态
*/
static final int INACTIVE = 1 << 31;
/**
* 常量值,空闲队列版本号的增量
*/
static final int SS_SEQ = 1 << 16;
/**
* 常量值,通过 & 计算队列是以先进先出(FIFO)的顺序还是以先进后出(LIFO)的顺序
*/
static final int MODE_MASK = 0xffff << 16;
/**
* 常量池,标识先进后出模式
*/
static final int LIFO_QUEUE = 0;
/**
* 常量值,标识先进先出模式
*/
static final int FIFO_QUEUE = 1 << 16;
/**
* 队列数组奇数索引处的队列称作内部队列(纯属个人理解),偶数索引处的队列称作共享队列
* 那么共享队列其实只用一个存放任务的队列,不存在自身的线程去处理它,所以它的所有任务都将被窃取,被内部队列窃取,因为内部队列有自身的线程,它会去窃取其他队列的任务
* 这也是其称作共享队列的缘由吧
*
* 常量值,标识共享队列,该队列不存在模式,只是被内部队列窃取任务
*/
static final int SHARED_QUEUE = 1 << 31;
/**
* 公共线程池
* 提供静态方法直接获取ForkJoinPool实例,该实例的运行状态不受调用shutdown或shutdownNow方法的影响,只有在程序关闭时才会关闭
*/
static final ForkJoinPool common;
/**
* 公共线程池的并行数
*/
static final int commonParallelism;
/**
* 公共线程池的最大备用线程数
*/
private static int commonMaxSpares;
/**
* 线程池的序号,通过从线程的名称可知是第几个线程池
*/
private static int poolNumberSequence;
/**
* 常量值,工作线程阻塞的时间,只有工作线程发生空闲时才阻塞
*/
private static final long IDLE_TIMEOUT = 2000L * 1000L * 1000L; // 2秒
/**
* 常量值,猜测是阻塞时间的误差
*/
private static final long TIMEOUT_SLOP = 20L * 1000L * 1000L; // 20ms
/**
* 常量池,公共线程池的默认备用线程数
*/
private static final int DEFAULT_COMMON_MAX_SPARES = 256;
/**
* 自旋次数,该值必须是2的幂次方,至少是4(不理解为什么一定要是2的幂次方)
* 一般发生自旋就是为了等待某种时刻的到来
*/
private static final int SPINS = 0;
/**
* 常量值,参与奇数索引的计算,防止发生碰撞
*/
private static final int SEED_INCREMENT = 0x9e3779b9;
/**
* 常量值,通过 & 计算ctl变量1-32位的值
*/
private static final long SP_MASK = 0xffffffffL;
/**
* 常量值,通过 & 计算ctl变量33-64位的值
*/
private static final long UC_MASK = ~SP_MASK;
/**
* 常量值,用于移位
*/
private static final int AC_SHIFT = 48;
/**
* 常量值,活跃的工作线程数的增量,也就是想要增加工作线程数的数量直接加上该值即可
*/
private static final long AC_UNIT = 0x0001L << AC_SHIFT;
/**
* 常量值,通过 & 计算活跃的工作线程数
*/
private static final long AC_MASK = 0xffffL << AC_SHIFT;
/**
* 常量值,用于移位
*/
private static final int TC_SHIFT = 32;
/**
* 常量值,工作线程数的增量
*/
private static final long TC_UNIT = 0x0001L << TC_SHIFT;
/**
* 常量值,通过 & 计算所有工作线程数
*/
private static final long TC_MASK = 0xffffL << TC_SHIFT;
/**
* 常量值,活跃的工作线程数的增量
*/
private static final long ADD_WORKER = 0x0001L << (TC_SHIFT + 15);
/**
* 常量值,标识线程池处于锁定状态
*/
private static final int RSLOCK = 1;
/**
* 常量值,标识线程池需要唤醒其他线程
*/
private static final int RSIGNAL = 1 << 1;
/**
* 常量值,标识线程池处于已启动状态
*/
private static final int STARTED = 1 << 2;
/**
* 常量值,标识线程池处于已停止状态
*/
private static final int STOP = 1 << 29;
/**
* 常量值,标识线程池处于已终止状态
*/
private static final int TERMINATED = 1 << 30;
/**
* 常量值,标识线程池处于已关闭状态
*/
private static final int SHUTDOWN = 1 << 31;
// 还有一些全局属性是通过Unsafe类来完成赋值的,实际上利用了CAS特性以原子化的方式去变更线程池的相关属性值,至于是如何使用的将会另外起文章介绍!!!
}
构造函数
/**
* 静态代码块
* 构建公共线程池
*/
static {
// ... 省略一些Unsafe使用的方法
commonMaxSpares = DEFAULT_COMMON_MAX_SPARES;
defaultForkJoinWorkerThreadFactory = new DefaultForkJoinWorkerThreadFactory();
modifyThreadPermission = new RuntimePermission("modifyThread");
// 构建公共线程池, 并行数是可用的处理器数量,也就是工作线程的个数是可用的处理器数量,任务以先进后出的顺序被处理
common = java.security.AccessController.doPrivileged
(new java.security.PrivilegedAction<ForkJoinPool>() {
public ForkJoinPool run() {
return makeCommonPool();
}
});
int par = common.config & SMASK; // 获取公共线程池的并行数,即工作线程的个数
commonParallelism = par > 0 ? par : 1;
}
/**
* 初始化,任务以先进后出的顺序被处理
*/
public ForkJoinPool() {
this(Math.min(MAX_CAP, Runtime.getRuntime().availableProcessors()), defaultForkJoinWorkerThreadFactory, null, false);
}
/**
* 初始化,自定义并行数,任务以先进后出的顺序被处理
* @param parallelism 线程池的并行数,也就是工作线程的个数
*/
public ForkJoinPool(int parallelism) {
this(parallelism, defaultForkJoinWorkerThreadFactory, null, false);
}
/**
* 初始化
* @param parallelism 线程池的并行数,也就是工作线程的个数
* @param factory 工作线程的工厂类
* @param handler 异常未处理的默认处理器
* @param asyncMode 队列的模式,即任务要以先进先出还是先进后出的顺序被处理,true:先进先出 false:先进后出
*/
public ForkJoinPool(int parallelism, ForkJoinWorkerThreadFactory factory, UncaughtExceptionHandler handler, boolean asyncMode) {
this(checkParallelism(parallelism), checkFactory(factory), handler, asyncMode ? FIFO_QUEUE : LIFO_QUEUE, "ForkJoinPool-" + nextPoolId() + "-worker-");
checkPermission();
}
/**
* 初始化
* @param parallelism 线程池的并行数,也就是工作线程的个数
* @param factory 工作线程的工厂类
* @param handler 异常未处理的默认处理器
* @param mode 队列的模式,即任务要以先进先出还是先进后出的顺序被处理
* @param workerNamePrefix 工作线程的前缀名称
*/
private ForkJoinPool(int parallelism, ForkJoinWorkerThreadFactory factory, UncaughtExceptionHandler handler, int mode, String workerNamePrefix) {
this.workerNamePrefix = workerNamePrefix;
this.factory = factory; //
this.ueh = handler;
this.config = (parallelism & SMASK) | mode;
long np = (long)(-parallelism);
/**
* 上面介绍了该属性是线程池的控制中心
* 该属性既有表示活跃的工作线程数也有表示所有的工作线程数,不过就目前来说咱们还未有任务提交自然不存在任何的工作线程,而在初始化时指定了并行数,所以这两个表示都应该为负并行数
* 以下的计算即表示为负并行数
*/
this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
}
简单方法
/**
* 构建公共线程池
* 实际上先是获取参数随后初始化了线程池
* @return 公共线程池
*/
private static ForkJoinPool makeCommonPool() {
int parallelism = -1;
ForkJoinWorkerThreadFactory factory = null;
UncaughtExceptionHandler handler = null;
try {
String pp = System.getProperty("java.util.concurrent.ForkJoinPool.common.parallelism"); // 获取系统属性的并行数,默认情况下为null
String fp = System.getProperty("java.util.concurrent.ForkJoinPool.common.threadFactory"); // 获取系统属性的工作线程的工厂类,默认情况下为null
String hp = System.getProperty("java.util.concurrent.ForkJoinPool.common.exceptionHandler"); // 获取系统属性的异常未处理的默认处理器,默认情况下为null
if (pp != null)
parallelism = Integer.parseInt(pp);
if (fp != null)
factory = ((ForkJoinWorkerThreadFactory)ClassLoader.getSystemClassLoader().loadClass(fp).newInstance());
if (hp != null)
handler = ((UncaughtExceptionHandler)ClassLoader.getSystemClassLoader().loadClass(hp).newInstance());
} catch (Exception ignore) {
}
if (factory == null) {
if (System.getSecurityManager() == null)
factory = defaultForkJoinWorkerThreadFactory;
else // use security-managed default
factory = new InnocuousForkJoinWorkerThreadFactory();
}
if (parallelism < 0 && (parallelism = Runtime.getRuntime().availableProcessors() - 1) <= 0) // Runtime.getRuntime().availableProcessors()获取可用处理器的数量,该数值绝不会小于1
parallelism = 1;
if (parallelism > MAX_CAP) // 限制并行数的大小,实际上就是在限制工作线程数的个数
parallelism = MAX_CAP;
return new ForkJoinPool(parallelism, factory, handler, LIFO_QUEUE, "ForkJoinPool.commonPool-worker-"); // 工作线程处于自身队列中的任务时,任务以先进后出的顺序被处理
}
/**
* 代替已经从内部队列移除的任务
* 只有调用ForkJoinTask#fork时,任务才会被放入到内部队列中
* 这里只是提供了一种占位,除此之外什么都没有做
*/
static final class EmptyTask extends ForkJoinTask<Void> {
private static final long serialVersionUID = -7721805057305804111L;
/**
* 初始化
* 定义了任务正常执行的状态
*/
EmptyTask() {
status = ForkJoinTask.NORMAL;
}
/**
* 获取任务的执行结果
* @return 任务的执行结果
*/
public final Void getRawResult() {
return null;
}
/**
* 设置任务的执行结果
* @param x 任务的执行结果
*/
public final void setRawResult(Void x) {}
/**
* 任务是否执行成功
* @return 任务是否执行成功
*/
public final boolean exec() {
return true;
}
}
/**
* 校验并行数是否合理
* @param parallelism 并行数
* @return 并行数
*/
private static int checkParallelism(int parallelism) {
if (parallelism <= 0 || parallelism > MAX_CAP)
throw new IllegalArgumentException();
return parallelism;
}
public void execute(Runnable task) {
if (task == null)
throw new NullPointerException();
ForkJoinTask<?> job;
if (task instanceof ForkJoinTask<?>) // avoid re-wrap
job = (ForkJoinTask<?>) task;
else
job = new ForkJoinTask.RunnableExecuteAction(task); // 将其包装成指定任务类型
externalPush(job);
}
final void externalPush(ForkJoinTask<?> task) {
WorkQueue[] ws; WorkQueue q; int m;
int r = ThreadLocalRandom.getProbe(); //该方式是在获取随机数,不过需要注意的是不同线程间除了第一次调用以外,该数值都将不同,实际上在多个线程同时提交任务时为了呈现随机性,而该值针对同一个线程是相同的
int rs = runState;
/**
* 上面提到对于提交的任务来说,应该放在偶数索引的队列中,故而 m & r & SQMASK 表明随机选择偶数索引
* 但是这里有一个好奇的点就是取得最大的偶数索引是126,也就是说即使队列数组在大,最终也只能取到这么多,那么容量如此大的队列数组是否造成了内存的浪费???
* 当获取到偶数索引的队列时,剩下的就是把该任务放入到队列中
*
* 判断队列是否处于锁定状态,若是则说明有其他线程正在提交任务,为了减少冲突,则进入到externalSubmit方法中重新在随机选择
*/
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); // 简单点理解,putOrderedObject方法就是将 top = task
U.putOrderedInt(q, QTOP, s + 1); // 更新下一个要存储务的索引
U.putIntVolatile(q, QLOCK, 0); // 解锁队列
if (n <= 1)
signalWork(ws, q); // 任务量很少的情况下,线程会进入到空闲,所以当你提交新的任务进来时应该使线程活跃起来,唤醒它们要开始工作了
return;
}
U.compareAndSwapInt(q, QLOCK, 1, 0);
}
externalSubmit(task);
}
/**
* 该方法做了以下几件事:
* 1. 获取线程池的使用权,并初始化队列数组
* 2. 获取线程池的使用权,随机获取偶数索引,在其位置上创建队列
* 3. 由于并未修改线程池的队列数组,故而不需要其使用权,直接提交任务
* 4. 在这过程中若未获取到线程池的使用权,则需要重新随机获取偶数索引处的队列
*/
private void externalSubmit(ForkJoinTask<?> task) {
int r;
if ((r = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit(); // 初始化随机数
r = ThreadLocalRandom.getProbe();
}
for (;;) {
// ws:队列数组 q:偶数索引处的队列,有可能为null rs:线程池的状态 m:队列数组最后位置的索引,通过 & 计算用于随机获取偶数索引 k:偶数索引
WorkQueue[] ws; WorkQueue q; int rs, m, k;
boolean move = false; // 当偶数索引处的队列处于锁定状态时,针对新任务必须要重新随机获取队列,而该标识仅用于决定是否重新获取随机数
if ((rs = runState) < 0) { // runState表示线程池的状态,当小于0时表示线程池处于已关闭状态
tryTerminate(false, false);
throw new RejectedExecutionException(); //直接抛出异常来拒绝任务的提交
}
else if ((rs & STARTED) == 0 || ((ws = workQueues) == null || (m = ws.length - 1) < 0)) { // 不考虑已关闭/已终止/已停止的情况下,若结果为true表明线程池尚未初始化
int ns = 0;
rs = lockRunState(); // 先让线程池处于锁定状态,以便进行后续的初始化
try {
if ((rs & STARTED) == 0) {
U.compareAndSwapObject(this, STEALCOUNTER, null, new AtomicLong()); // 简单来说就是将 stealCounter = new AtomicLong
int p = config & SMASK; // ensure at least 2 slots
int n = (p > 1) ? p - 1 : 1;
/**
* 这一段的计算很有意思,实验几个例子就能够猜到做了什么操作!!!
* 通过上面的计算我们知道 n 是代表队列数组的长度大小,(这里先给出结论)根据该类内部的注释我们可以知道该队列的长度应该是2的幂次方
* 那为什么是2的幂次方呢? 有一段注释如此写到:
* To simplify index-based operations, the array size is always a power of two, and all readers must tolerate null slots
* 所以我们可以知道这样子做是为了简化基于索引的操作
*
* 回到正题,既然需要2的幂次方,有如此步骤:
* 1. 首先就需要将其对应的二进制全部变成1,有点难理解,我们举例子吧,假设 n = 0000 0000 0000 0000 0000 1001 1011 1101
* 那么按照我们的想法最终应该是变成 n = 0000 0000 0000 0000 0000 1111 1111 1111,即把最高位(从左到右以1开始的位)的后续所有数字全部变成1就可以了
* 所有 n |= n >>> 1 - n |= n >>> 16 就是为了变成上面的结果,当然了,上面的结果很明显不是2的幂次方,所以就有了接下来的内容
* 2. 对于 n = 0000 0000 0000 0000 0000 1111 1111 1111 这样子的数字在 + 1 后就变成了 n = n = 0000 0000 0000 0000 0001 0000 0000 0000, 这样子就变成了2的幂次方
* 这种计算方式在ArrayList中应该介绍过,既然已经是2的幂次方了,为什么后续还要左移1位呢,其实上面的注释也说了,确保至少有2个插槽,为什么要确保有2个插槽呢,因为该方法被调用表示已经有任务提交了
* 那么队列总不能为空吧,或者是2的0次幂方吧,所以队列的大小至少是2的1次幂方,故而又左移1位了
*
* 总结:该片段代码是为了使队列的长度大小形成2的幂次方,至少是2的1次幂方,如此行为是为了简化基于索引的操作
*/
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
n = (n + 1) << 1;
workQueues = new WorkQueue[n]; // 构建队列数组,其最大值是SMASK
ns = STARTED;
}
} finally {
unlockRunState(rs, (rs & ~RSLOCK) | ns); // 将线程池的状态由于RSLOCK更换成STARTED,相当于在说明线程池我已经使用完毕了,其他线程可以去抢占了,故在最后会唤醒其他线程
}
}
else if ((q = ws[k = r & m & SQMASK]) != null) { // 走到这里说明线程池已经初始化过了,而能够调用externalSubmit方法的是提交了外部任务(执行execute/submit),这样子的行为需要查找偶数索引的队列,即共享队列
/**
* 现在已经随机获取到偶数索引的队列,那么还要查看此队列是否处于锁定状态,即是否有任务正在提交
* 若有任务正在提交中,则修改标志位move,以便在下次for循环时重新选择新的队列进行任务的提交
* 若未有任务在使用该队列,那么先将其标志位修改成锁定状态,以便后续的继续执行
* 查看队列是否处于锁定状态用qlock标志位
*/
if (q.qlock == 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) { // 将此队列修改成锁定状态
ForkJoinTask<?>[] a = q.array;
int s = q.top;
boolean submitted = false; // initial submission or resizing
try { // locked version of push
if ((a != null && a.length > s + 1 - q.base) || (a = q.growArray()) != null) {
int j = (((a.length - 1) & s) << ASHIFT) + ABASE;
U.putOrderedObject(a, j, task); // 简单理解来说就是将该任务放到任务数组的top索引上
U.putOrderedInt(q, QTOP, s + 1); // 更新top值,以便存储下个任务
submitted = true;
}
} finally {
U.compareAndSwapInt(q, QLOCK, 1, 0); // 任务已经填充完毕,应当解锁队列,以便其他任务可以使用该队列
}
if (submitted) {
signalWork(ws, q);
return;
}
}
move = true; // move on failure
}
else if (((rs = runState) & RSLOCK) == 0) { // 走到这里说明,偶数索引位置并未存在共享队列,故而需要创建一个,同时需要拥有线程池的使用权
q = new WorkQueue(this, null);
q.hint = r;
q.config = k | SHARED_QUEUE; // k:偶数索引 config:偶数索引 + 模式
q.scanState = INACTIVE; // 队列处于空闲状态
rs = lockRunState(); // 获取线程池的使用权,因为后续要将该队列放入到队列数组中了
if (rs > 0 && (ws = workQueues) != null && k < ws.length && ws[k] == null)
ws[k] = q;
unlockRunState(rs, rs & ~RSLOCK); // 线程池使用完毕,进行解锁,其他线程可以去抢占了
}
else
move = true; // 未拥有线程池的使用权且偶数索引处的队列是null则需要重新随机获取偶数索引的队列
if (move)
r = ThreadLocalRandom.advanceProbe(r); // 简单理解就是重新取随机数
}
}
/**
* 新增或唤醒线程处理任务
* 调用此方法说明当前队列中存在任务
* @param ws 队列数组
* @param q 当前队列
*/
final void signalWork(WorkQueue[] ws, WorkQueue q) {
long c; int sp, i; WorkQueue v; Thread p;
while ((c = ctl) < 0L) { // ctl的值在初始化时进行赋值,若 ctl < 0 表示活跃的工作线程数量过少,不满足并行数,所以必须要增加活跃的工作线程数量
if ((sp = (int)c) == 0) { // 强制转换类型取 1- 32位,根据ctrl的1-32位可知是空闲队列的版本计数与空闲队列的索引
if ((c & ADD_WORKER) != 0L) // ctl & ADD_WORKER 相当于在获取所有的工作线程数,若该结果是1则表示所有的工作线程数过少,若结果是0则说明所有的工作线程数已足够,所以不等于0就相当于在判断是否过少
tryAddWorker(c); // 若所有的工作线程数过少则必须要进行添加
break;
}
// 走到这里说明有空闲队列
if (ws == null) // 个人认为这么不会发生,因为 ws 引用的是 workQueues 对象,而在全部的代码中并未找到其赋成null值
break;
if (ws.length <= (i = sp & SMASK)) // sp & SMASK是在获取空闲队列的索引,ws.length <= i 只有在线程池已关闭的情况下才会发生,因为关闭时清空了队列数组
break;
if ((v = ws[i]) == null) // 关闭时清空了队列数组,导致为null
break;
int vs = (sp + SS_SEQ) & ~INACTIVE; // 计算空闲队列的版本计数与活跃状态
int d = sp - v.scanState; // 判断空闲队列的行为是否发生了,行为指的是版本计数和状态,若发生了改变则需要重新计算
long nc = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & v.stackPred); // 活跃的工作线程数加1,计算上一个最新的空闲队列
if (d == 0 && U.compareAndSwapLong(this, CTL, c, nc)) {
v.scanState = vs; // 使空闲队列变成活跃状态
if ((p = v.parker) != null) // 若此时空闲队列处于阻塞中,则立即唤醒,告诉它起来干活了
U.unpark(p);
break;
}
if (q != null && q.base == q.top) // base == top 时表明队列中的所有任务都处理完毕了
break;
}
}
/**
* 新增一个或多个工作线程,使其满足并行数,即所有的工作线程数应该等于并行数
* @param c ctl值
*/
private void tryAddWorker(long c) {
boolean add = false;
do {
long nc = ((AC_MASK & (c + AC_UNIT)) | (TC_MASK & (c + TC_UNIT))); // 添加新的工作线程,所以ctl对应的值应该有所变化,通过移位的方式进行增加,最终活跃的工作线程数与所有的工作线程数都加上1
if (ctl == c) { // 这里加上判断是为了在多线程下ctl发生改变,而造成c != ctl ,所以必须要拿到最新的ctl之后在进行计算,即在while当中进行赋值后重新计算
int rs, stop;
if ((stop = (rs = lockRunState()) & STOP) == 0) // 先将线程池置于锁定状态下,同时判断当前线程池是否处于已关闭状态下(STOP),若是则结果false,随后退出循环,若不处于已关闭状态下,则修改ctl = nc
add = U.compareAndSwapLong(this, CTL, c, nc);
unlockRunState(rs, rs & ~RSLOCK); // ctl的值已修改完毕,进行解锁
if (stop != 0)
break;
if (add) {
createWorker(); // 创建新的工作线程并启动
break;
}
}
/**
* 先判断所有的工作线程数是否满足并行数,若不满足还要判断线程池是否很忙碌,即是否存在空闲线程
* 若存在则说明不忙碌,就没必要在创建新的工作线程了,不然要浪费资源了
* 若不存在则说明很忙碌,需要创建多个工作线程来同时工作,以提供工作效率
*/
} while (((c = ctl) & ADD_WORKER) != 0L && (int)c == 0);
}
/**
* 创建工作线程并开启
* @return 结果值
*/
private boolean createWorker() {
ForkJoinWorkerThreadFactory fac = factory; // 工作线程的工厂类在初始化就已经赋值了
Throwable ex = null;
ForkJoinWorkerThread wt = null;
try {
if (fac != null && (wt = fac.newThread(this)) != null) { // 创建工作线程
wt.start();
return true;
}
} catch (Throwable rex) {
ex = rex;
}
deregisterWorker(wt, ex); // 若工作线程创建或启动失败的话,则废弃掉已创建的对应的队列,在根据满足条件选择要唤醒空闲线程或重新创建工作线程,但最终都会抛出异常
return false;
}
/**
* 为新创建的工作线程设置对应的队列,该队列处于队列数组的奇数索引上
* 该方法只会在ForkWokerThread初始化时调用
* @param wt 工作线程
* @return 奇数索引上的队列
*/
final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
UncaughtExceptionHandler handler;
wt.setDaemon(true);
if ((handler = ueh) != null)
wt.setUncaughtExceptionHandler(handler);
WorkQueue w = new WorkQueue(this, wt);
int i = 0;
int mode = config & MODE_MASK; // 获取模式,即要以FIFO还是LIFO
int rs = lockRunState();
try {
WorkQueue[] ws; int n;
if ((ws = workQueues) != null && (n = ws.length) > 0) {
int s = indexSeed += SEED_INCREMENT; // 这里就不过多猜想它为什么这么做,感觉像是在做随机化
int m = n - 1;
i = ((s << 1) | 1) & m; // 随机获取奇数索引
if (ws[i] != null) { // 判断奇数索引是否已经有存在队列了,若是有队列的话,则需要重新获取
/**
* 计算step,用于后续重新获取奇数索引,相当于在原来获取到的奇数索引上往前移动计算后的step,即让其往前推
* 该step的结果一定是偶数,因为只有这样子才能使得 奇数 + 偶数的结果还是奇数,其step大约是队列数组的一半
*
* 但是在往前推的过程中,有可能出现队列数组的大部分奇数索引都存在队列,导致要一直重复性查找,这样子说明现有的队列数组过小
* 所以使用了probes作为其标识,当其重复查找的次数大于队列数组的容量时,就将其进行扩容,用这个标准主要是说明它循环过一次队列数组了,但还是找不到适合的索引处,建议还是直接扩容吧
*/
int probes = 0;
int step = (n <= 4) ? 2 : ((n >>> 1) & EVENMASK) + 2;
while (ws[i = (i + step) & m] != null) { //
if (++probes >= n) {
workQueues = ws = Arrays.copyOf(ws, n <<= 1);
m = n - 1;
probes = 0;
}
}
}
w.hint = s;
w.config = i | mode;
w.scanState = i; // 队列处于运行中
ws[i] = w;
}
} finally {
unlockRunState(rs, rs & ~RSLOCK);
}
wt.setName(workerNamePrefix.concat(Integer.toString(i >>> 1)));
return w;
}
/**
* 注销工作线程
* 该方法会在创建工作线程失败或启动失败时或关闭活跃的工作线程时被调用
* @param wt 工作线程,可能null
* @param ex 异常信息,可能为null
*/
final void deregisterWorker(ForkJoinWorkerThread wt, Throwable ex) {
WorkQueue w = null;
if (wt != null && (w = wt.workQueue) != null) {
WorkQueue[] ws;
int idx = w.config & SMASK; // 获取工作线程对应的队列的奇数索引,以便从队列数组中移除
int rs = lockRunState();
if ((ws = workQueues) != null && ws.length > idx && ws[idx] == w)
ws[idx] = null;
unlockRunState(rs, rs & ~RSLOCK);
}
long c;
do {} while (!U.compareAndSwapLong(this, CTL, c = ctl, ((AC_MASK & (c - AC_UNIT)) | (TC_MASK & (c - TC_UNIT)) | (SP_MASK & c)))); // 将活跃的工作线程数与所有的工作线程数都减去1
if (w != null) { // 既然要注销工作线程,其对应的队列应该被废弃掉
w.qlock = -1; // 标识队列被废弃
w.transferStealCount(this); // 更新已窃取的任务数量
w.cancelAll(); // 取消所有任务,包括窃取的任务
}
for (;;) {
WorkQueue[] ws; int m, sp;
// 判断线程池是否正在被关闭,若是正在被关闭则没必要新增工作线程或唤醒空闲队列
if (tryTerminate(false, false) || w == null || w.array == null || (runState & STOP) != 0 || (ws = workQueues) == null || (m = ws.length - 1) < 0)
break;
if ((sp = (int)(c = ctl)) != 0) {
if (tryRelease(c, ws[sp & m], AC_UNIT)) // 尝试唤醒最新的空闲队列
break;
}
else if (ex != null && (c & ADD_WORKER) != 0L) { // 若不存在空闲的工作队列说明线程池可能处于忙碌中,接着判断是否满足并行数,不满足则添加新的工作线程来提高任务的处理速度
tryAddWorker(c);
break;
}
else
break;
}
if (ex == null)
ForkJoinTask.helpExpungeStaleExceptions(); // 成功关闭活跃的工作线程时会走到这里,关于ForkJoinTask的方法将会另外新起文章进行介绍
else
ForkJoinTask.rethrow(ex); // 创建工作线程或启动失败将会抛出异常,既然失败了但是我们已经尝试重新创建,但它最终还是会抛出异常,没太想明白...
}
/**
* 执行窃取的任务、自身队列的任务,当没有任务的情况下阻塞空闲线程
* @param w 当前队列
*/
final void runWorker(WorkQueue w) {
w.growArray(); // 初始化或扩容任务数组
int seed = w.hint;
int r = (seed == 0) ? 1 : seed; // 随机数
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; // 随机数
}
}
/**
* 随机窃取队列的任务,有可能窃取到的任务是自身队列的任务
* 多次尝试窃取任务,若没有窃取到任务则将当前队列置为空闲状态并再次遍历一次队列数组尝试着窃取任务,若还是没有的话就返回null,若有任务可以被窃取则需要唤醒最新的空闲线程,由于多线程的缘故可能不是当前队列对应的线程
* 所以有可能到头来又为其他线程做了嫁衣
* 且当中会检查是否存在多个任务,则会唤醒空闲线程或新增工作线程
* 关联每个空闲队列,通过空闲队列能获取到空闲线程
* @param w 当前队列
* @param r 随机数
* @return 窃取到的任务或null
*/
private ForkJoinTask<?> scan(WorkQueue w, int r) {
WorkQueue[] ws; int m;
if ((ws = workQueues) != null && (m = ws.length - 1) > 0 && w != null) {
int ss = w.scanState; // 队列在队列数组所处的奇数索引
/**
* r:随机数
* origin:起始查找的索引值
* k:当前查找的索引值 当k == origin说明已经遍历完整个队列数组
* oldSum:上一次遍历的索引数量
* checkSum:遍历的索引值,每获取一个不为空的队列,该数值都会增加
* 1. 当遍历完整个队列数组后,会比较 oldSum 与 checkSum 是否相等,若相等则说明并未有任务可以被窃取,则将 oldSum = checkSum,checkSum = 0, 接着会在遍历一次,再次确认是否有任务可以被窃取
* 1.1 若没有则将当前队列置为空闲状态,不去窃取了,而在置完之后还会再去遍历一次,如果这个时候恰好有任务了,则会重新开始窃取并唤醒空闲线程,若还是没有任务,则最终当前队列对应的线程将变成空闲线程
* 2. 若不相等则说明有任务可以窃取,继续遍历
*/
for (int origin = r & m, k = origin, oldSum = 0, checkSum = 0;;) {
WorkQueue q; ForkJoinTask<?>[] a; ForkJoinTask<?> t; // q:队列 a:队列中的任务数组 t:任务
int b, n; long c; // b:q.base n:q.base - q.top c:ctl
if ((q = ws[k]) != null) { // 查看当前索引位置是否有队列,若有则进入,若没有则继续找下一个连续索引,即假设当前索引是1,那么下一个应该找索引为2
if ((n = (b = q.base) - q.top) < 0 && (a = q.array) != null) { // 判断队列中是否有任务存在,若top > base 说明有任务存在
long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
if ((t = ((ForkJoinTask<?>) U.getObjectVolatile(a, i))) != null && q.base == b) { // 获取任务, 当base != b时说明其他线程正在窃取该任务,为了降低冲突,选择重新开始随机窃取
if (ss >= 0) {
if (U.compareAndSwapObject(a, i, t, null)) { // 将队列数组中的任务置为null
q.base = b + 1;
if (n < -1) // 当 n < -1 时表示有多个任务,需要去新增工作线程或唤醒空闲线程
signalWork(ws, q);
return t;
}
}
else if (oldSum == 0 && w.scanState < 0)
// 唤醒最新的空闲线程,即对应到最新的队列,每个队列之间用stackPred关联,个人认为先唤醒最新的空闲线程是因为其等待的时间最长,减少等待时间,提高窃取任务的效率
tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
}
if (ss < 0)
ss = w.scanState;
r ^= r << 1; r ^= r >>> 3; r ^= r << 10;
origin = k = r & m; // 重新开始窃取任务
oldSum = checkSum = 0;
continue;
}
checkSum += b;
}
if ((k = (k + 1) & m) == origin) { // 当 k == origin 表明已经遍历了整个任务数组都没有发现任务
if ((ss >= 0 || (ss == (ss = w.scanState))) && oldSum == (oldSum = checkSum)) { // 若队列已经处于空闲状态,则没必要在处理了,若队列处于运行中,在多次尝试窃取不到任务后说明没有任务,队列要进入空闲状态
if (ss < 0 || w.qlock < 0) // 在多次窃取不到任务后会从这里退出循环并返回null
break;
int ns = ss | INACTIVE; // 索引 + INACTIVE < 0
long nc = ((SP_MASK & ns) | (UC_MASK & ((c = ctl) - AC_UNIT))); // 活跃的工作线程数减去1
w.stackPred = (int)c; // 关联当前的空闲队列(通过空闲队列可以找到空闲线程-parker),以便在后续可以唤醒最新的空闲线程
U.putInt(w, QSCANSTATE, ns); // 使队列处于空闲状态
if (U.compareAndSwapLong(this, CTL, c, nc)) // 更新ctl值,其中修改了活跃的工作线程数、空闲队列在队列数组中的索引及状态(1-32位)
ss = ns;
else
w.scanState = ss;
}
checkSum = 0;
}
}
}
return null;
}
/**
* 等待任务-阻塞最新的空闲线程
* 当阻塞一定时间后仍然没有任务的话,则会注销最新的空闲线程,并更新最新的空闲线程引用
* 当阻塞一定时间后有任务了,则跳出循环去获取任务
* @param w 当前空闲队列
* @param r 随机数
* @return false:关闭当前队列对应的线程 true:不能关闭
*/
private boolean awaitWork(WorkQueue w, int r) {
if (w == null || w.qlock < 0) // 队列处于终止状态
return false;
for (int pred = w.stackPred, spins = SPINS, ss;;) { // pred:上一个最新的空闲线程 spins = 自旋次数
if ((ss = w.scanState) >= 0)
break;
else if (spins > 0) { // 默认情况下SPINS = 0,若SPINS > 0 则通过自旋的方式来阻塞空闲线程,而当空闲线程变成活跃后才会退出循环
r ^= r << 6; r ^= r >>> 21; r ^= r << 7;
if (r >= 0 && --spins == 0) {
WorkQueue v; WorkQueue[] ws; int s, j; AtomicLong sc;
if (pred != 0 && (ws = workQueues) != null && (j = pred & SMASK) < ws.length &&
(v = ws[j]) != null && (v.parker == null || v.scanState >= 0))
spins = SPINS;
}
}
else if (w.qlock < 0)
return false;
else if (!Thread.interrupted()) {
long c, prevctl, parkTime, deadline;
int ac = (int)((c = ctl) >> AC_SHIFT) + (config & SMASK); // 活跃的工作线程数
if ((ac <= 0 && tryTerminate(false, false)) || (runState & STOP) != 0) // 当活跃的工作线程数不足时,判断线程池是否处于关闭状态或正在关闭(SHUTDOWN/STOP/TERMINAED),若是的话就没必要进行后续的阻塞了
return false;
if (ac <= 0 && ss == (int)c) { // 当前空闲线程是否是最新的空闲线程,即表示刚使其空闲
prevctl = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & pred); // 上一个最新的ctl
int t = (short)(c >>> TC_SHIFT); // 计算所有的工作线程数
if (t > 2 && U.compareAndSwapLong(this, CTL, c, prevctl)) // 若工作线程数过多,加上此时还是空闲的线程,则应该关闭一些线程
return false; // else use timed wait
parkTime = IDLE_TIMEOUT * ((t >= 0) ? 1 : 1 - t); // 计算当前空闲线程的阻塞时间
deadline = System.nanoTime() + parkTime - TIMEOUT_SLOP; // 计算当前的阻塞时间
}
else
prevctl = parkTime = deadline = 0L;
Thread wt = Thread.currentThread();
U.putObject(wt, PARKBLOCKER, this);
w.parker = wt;
if (w.scanState < 0 && ctl == c)
U.park(false, parkTime); // 发生阻塞
U.putOrderedObject(w, QPARKER, null);
U.putObject(wt, PARKBLOCKER, null);
if (w.scanState >= 0) // 阻塞一段时间后,若有任务会被唤醒,需要去处理任务了
break;
if (parkTime != 0L && ctl == c && deadline - System.nanoTime() <= 0L && U.compareAndSwapLong(this, CTL, c, prevctl)) // 阻塞一段时间后仍然没有任务,且当前空闲线程是最新的空闲线程,返回false减少线程
return false;
}
}
return true;
}
/**
* 阻塞等待指定任务完成或超时
* 该方法由ForkJoinTask#join -> ForkJoinTask#doJoin中调用
* 提醒一下,对于ForkJoinTask#fork和ForkJoinTask#join同一个任务来说,可根据其任务的状态来判断任务是否完成
* 当任务的状态小于0时表示处理完毕,当然可能是被取消了、抛出异常了、正常处理,所以在业务上最好还要在判断下,不用担心,ForkJoinTask提供了方法isCompletedAbnormally
* @param w 当前队列
* @param task 指定任务
* @param deadline 阻塞等待时间
* @return 任务执行结果
*/
final int awaitJoin(WorkQueue w, ForkJoinTask<?> task, long deadline) {
int s = 0;
if (task != null && w != null) {
ForkJoinTask<?> prevJoin = w.currentJoin;
U.putOrderedObject(w, QCURRENTJOIN, task);
CountedCompleter<?> cc = (task instanceof CountedCompleter) ? (CountedCompleter<?>)task : null;
for (;;) {
if ((s = task.status) < 0) // status < 0 表示任务已处理完毕
break;
if (cc != null)
helpComplete(w, cc, 0);
else if (w.base == w.top || w.tryRemoveAndExec(task)) // 表示任务数组为空,说明指定任务被其他线程窃取了,当前线程只能干等着,于其如此还不如去窃取其他线程,帮帮忙,哈哈哈,等窃取的任务执行完毕了在回过头来看看
helpStealer(w, task); // 窃取任务并执行
if ((s = task.status) < 0) // 判断指定任务是否处理完毕
break;
long ms, ns;
if (deadline == 0L)
ms = 0L;
else if ((ns = deadline - System.nanoTime()) <= 0L)
break;
else if ((ms = TimeUnit.NANOSECONDS.toMillis(ns)) <= 0L)
ms = 1L;
if (tryCompensate(w)) { // 为了保证阻塞等待的任务能够被线程及时处理,那么一定要有线程处于活跃中,若是补偿的话(叫补偿感觉有点变扭),要么是唤醒空闲线程要么是新增工作线程,而不补偿的话说明本身就存在工作线程
task.internalWait(ms); // 内部使用了Object#wait使其处于阻塞等待,当任务完成后会超过指定时间后会被唤醒
U.getAndAddLong(this, CTL, AC_UNIT); // 既然补偿了,那么就要增加活跃的工作线程数了
}
}
U.putOrderedObject(w, QCURRENTJOIN, prevJoin);
}
return s;
}
/**
* 判断是否需要补偿工作线程,简单来说就是保证阻塞等待的任务能够被线程及时处理
* 补偿可以通过唤醒空闲线程,也可以是创建新线程,而不需要补偿说明本身就存在有使用中的线程(奇数索引上队列处于运行中状态)
* @param w 当前队列
* @return 是否需要补偿
*/
private boolean tryCompensate(WorkQueue w) {
boolean canBlock;
WorkQueue[] ws; long c; int m, pc, sp;
if (w == null || w.qlock < 0 || (ws = workQueues) == null || (m = ws.length - 1) <= 0 || (pc = config & SMASK) == 0)
canBlock = false;
else if ((sp = (int)(c = ctl)) != 0)
canBlock = tryRelease(c, ws[sp & m], 0L); // 唤醒空闲线程去处理任务
else {
int ac = (int)(c >> AC_SHIFT) + pc;
int tc = (short)(c >> TC_SHIFT) + pc; // pc:并行数,工作线程数 (short)(c >> TC_SHIFT):线程池所有的工作线程数,不满足并行数情况下是个负数,满足的情况是正数,所以为了方便理解,可以将tc也看作是并行数,即 tc == pc
int nbusy = 0; // 计算队列处于忙碌状态的个数,偶数索引上队列的状态是INACTIVE,奇数索引上队列的状态不确定,所以在遍历过程中只要判断奇数索引上队列的状态即可
for (int i = 0; i <= m; ++i) {
WorkQueue v;
if ((v = ws[((i << 1) | 1) & m]) != null) {
if ((v.scanState & SCANNING) != 0)
break;
++nbusy;
}
}
/**
* nbusy的最大值是队列数组的长度- 1,即ws.lenght - 1
* tc << 1的计算也是队列数组的长度 - 1,从externalSubmit方法初始化队列数组中得知并行数与队列数组的长度 - 1呈现 2 倍关系
* 所以如果两者不相等的话说明nbusy没达到最大值,即存在至少一个奇数索引上队列处于运行中状态,表明有线程去处理任务,不用创建新线程
*/
if (nbusy != (tc << 1) || ctl != c)
canBlock = false;
// tc > pc, 说明 (short)(c >> TC_SHIFT) > 0 ,这不就造成所有的工作线程数大于并行数了吗? 那么势必会调用createWorker,也在源码中也只有tryCompensate方法会造成所有的工作线程数直接增加,而不经过是否大于并行数的判断
else if (tc >= pc && ac > 1 && w.isEmpty()) { // 队列都为空了,没必要补偿活跃的工作线程了
long nc = ((AC_MASK & (c - AC_UNIT)) | (~AC_MASK & c)); // uncompensated
canBlock = U.compareAndSwapLong(this, CTL, c, nc);
}
else if (tc >= MAX_CAP || (this == common && tc >= pc + commonMaxSpares))
throw new RejectedExecutionException("Thread limit exceeded replacing blocked worker");
else { // 走到这里说明没有空闲线程或所有线程都处于忙碌中,但是必须保证有线程去及时处理等待中的任务,所以要新增一个线程
boolean add = false; int rs;
long nc = ((AC_MASK & c) | (TC_MASK & (c + TC_UNIT))); // 所有的工作线程数加1
if (((rs = lockRunState()) & STOP) == 0)
add = U.compareAndSwapLong(this, CTL, c, nc);
unlockRunState(rs, rs & ~RSLOCK);
canBlock = add && createWorker(); // 增加工作线程
}
}
return canBlock;
}
/**
* 在指定时间内使公共线程池处于静止下
* 静止:所有的工作线程处于空闲或者说没有活跃的工作线程
* @param timout 指定时间
* @param unit 时间单位
* @return true 公共线程处处于静止 false:未在指定时间内使公共线程池处于静止下
*/
public boolean awaitQuiescence(long timeout, TimeUnit unit) {
long nanos = unit.toNanos(timeout);
ForkJoinWorkerThread wt;
Thread thread = Thread.currentThread();
if ((thread instanceof ForkJoinWorkerThread) &&
(wt = (ForkJoinWorkerThread)thread).pool == this) {
helpQuiescePool(wt.workQueue); // 该方法就不做解释了,有点绕,简单理解就是执行队列自身的任务,并窃取其他队列的任务
return true;
}
long startTime = System.nanoTime();
WorkQueue[] ws;
int r = 0, m;
boolean found = true;
while (!isQuiescent() && (ws = workQueues) != null && (m = ws.length - 1) >= 0) {
if (!found) {
if ((System.nanoTime() - startTime) > nanos)
return false;
Thread.yield(); // cannot block
}
found = false;
for (int j = (m + 1) << 2; j >= 0; --j) {
ForkJoinTask<?> t; WorkQueue q; int b, k;
if ((k = r++ & m) <= m && k >= 0 && (q = ws[k]) != null &&
(b = q.base) - q.top < 0) {
found = true;
if ((t = q.pollAt(b)) != null)
t.doExec(); // 帮助窃取任务并执行
break;
}
}
}
return true;
}
/**
* 关闭线程池
* @param noew 是否是无条件关闭线程池
* @param enable 是否允许关闭线程池
* @return 线程池是否关闭成功
*/
private boolean tryTerminate(boolean now, boolean enable) {
int rs;
if (this == common) // 公共线程池不受状态的影响,只会随着程序的关闭而关闭
return false;
if ((rs = runState) >= 0) { // runState > 0 说明线程池处于启动中
if (!enable) // enable表示是否允许关闭线程池
return false;
rs = lockRunState(); // 若是允许关闭线程的话,就将其状态 + SHUTDOWN,此时的状态 < 0
unlockRunState(rs, (rs & ~RSLOCK) | SHUTDOWN);
}
if ((rs & STOP) == 0) {
if (!now) { // now表示是否是无条件关闭线程池,是有条件关闭线程池的话说明是没有任务或无活跃的工作线程
for (long oldSum = 0L;;) { // repeat until stable
WorkQueue[] ws; WorkQueue w; int m, b; long c;
long checkSum = ctl;
if ((int)(checkSum >> AC_SHIFT) + (config & SMASK) > 0) // > 0 表示仍然有活跃的工作线程
return false;
if ((ws = workQueues) == null || (m = ws.length - 1) <= 0)
break;
for (int i = 0; i <= m; ++i) {
if ((w = ws[i]) != null) {
if ((b = w.base) != w.top || w.scanState >= 0 || w.currentSteal != null) {
tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
return false;
}
checkSum += b;
if ((i & 1) == 0) // 为偶数索引上的队列做标记
w.qlock = -1;
}
}
if (oldSum == (oldSum = checkSum))
break;
}
}
if ((runState & STOP) == 0) {
rs = lockRunState(); // 状态 + SHUTDOWN + STOP
unlockRunState(rs, (rs & ~RSLOCK) | STOP);
}
}
int pass = 0; // 使用3个步骤来帮助关闭线程池
for (long oldSum = 0L;;) {
WorkQueue[] ws; WorkQueue w; ForkJoinWorkerThread wt; int m;
long checkSum = ctl;
if ((short)(checkSum >>> TC_SHIFT) + (config & SMASK) <= 0 || (ws = workQueues) == null || (m = ws.length - 1) <= 0) {
if ((runState & TERMINATED) == 0) {
rs = lockRunState(); // done
unlockRunState(rs, (rs & ~RSLOCK) | TERMINATED); // 状态 + SHUTDOWN + STOP + TERMINATED
synchronized (this) { notifyAll(); } // for awaitTermination
}
break;
}
for (int i = 0; i <= m; ++i) {
if ((w = ws[i]) != null) {
checkSum += w.base;
w.qlock = -1;
if (pass > 0) {
w.cancelAll(); // 清空队列的任务
if (pass > 1 && (wt = w.owner) != null) {
if (!wt.isInterrupted()) {
try {
wt.interrupt(); // 中断awaitJoin中发生的阻塞等待
} catch (Throwable ignore) {
}
}
if (w.scanState < 0)
U.unpark(wt);
}
}
}
}
if (checkSum != oldSum) {
oldSum = checkSum;
pass = 0;
}
else if (pass > 3 && pass > m)
break;
else if (++pass > 1) {
long c; int j = 0, sp;
while (j++ <= m && (sp = (int)(c = ctl)) != 0) // 因为空闲线程会造成程序的阻塞,所以在线程池要关闭的情况下将其唤醒,释放线程
tryRelease(c, ws[sp & m], AC_UNIT);
}
}
return true;
}
/**
* 获取线程池的使用权,即使线程池处于锁定状态
* 若线程池处于已经处于锁定状态,那么当前线程将通过自旋 + 阻塞的方式一直获取
* @return 线程池的状态(锁定状态 + 已有的状态 STARTED/STOP/TERMINATED/SHUTDOWN)
*/
private int lockRunState() {
int rs;
return ((((rs = runState) & RSLOCK) != 0 || !U.compareAndSwapInt(this, RUNSTATE, rs, rs |= RSLOCK)) ? awaitRunStateLock() : rs);
}
/**
* 获取线程池的使用权,通过自旋 + 阻塞的方式直到获取到
* 该方法只会在线程池处于锁定状态(RSLOCK)或CAS失败的情况下调用
* @return 线程池处于锁定的状态值
*/
private int awaitRunStateLock() {
Object lock;
boolean wasInterrupted = false;
for (int spins = SPINS, r = 0, rs, ns;;) { // 通过自旋 + 阻塞的方式等待线程池的使用权,即获取线程池的锁定状态
if (((rs = runState) & RSLOCK) == 0) { // 表示线程池未锁定,那么当前线程可以获取到线程池的使用权了
if (U.compareAndSwapInt(this, RUNSTATE, rs, ns = rs | RSLOCK)) { // 既然当前线程可以拥有线程池的使用权了,防止其他线程介入应当更改其状态为RSLOCK
if (wasInterrupted) { // 此判断用于在当前线程发生阻塞时而被中断,即后续的lock.wait代码片段,发生中断会抛出异常并清除中断状态,所以这里的代码只是恢复中断状态
try {
Thread.currentThread().interrupt();
} catch (SecurityException ignore) {
}
}
return ns;
}
}
else if (r == 0)
r = ThreadLocalRandom.nextSecondarySeed(); //每次自旋后都重新获取随机数,目的就是为了下面的计算实现随机化
/**
* 默认情况下SPINS为0,减少CPU的使用,也就是说当有多个线程同时在抢占线程池的使用权时,未抢到的线程应该不在执行任何逻辑操作,相当于等待,至于为什么要抢占线程池的使用权是因为这些线程会同时更新线程池的队列数组
* 有一点比较好奇,并未提供对SPINS属性的修改方法,所以这里的判断语句压根就不会被执行...
*/
else if (spins > 0) {
r ^= r << 6; r ^= r >>> 21; r ^= r << 7; // 此算法不知道做了什么操作,通过阅读注释可知是在做随机化
if (r >= 0)
--spins;
}
else if ((rs & STARTED) == 0 || (lock = stealCounter) == null)
Thread.yield(); // 不考虑线程池已关闭/已终止/已停止的状态下,说明线程池处于锁定状态(RSLOCK)
else if (U.compareAndSwapInt(this, RUNSTATE, rs, rs | RSIGNAL)) {
synchronized (lock) { // 不考虑线程池已关闭/已终止/已停止的状态下,说明线程池处于已开启状态(STARTED),以及标识其他线程正在等待中
if ((runState & RSIGNAL) != 0) { // 再次判断线程池的状态,此时只会有一个线程进入到该代码片段中,而对于其他线程来说,经过短暂的获取锁时有可能线程池的状态已经发生改变
try {
lock.wait();
} catch (InterruptedException ie) {
if (!(Thread.currentThread() instanceof ForkJoinWorkerThread))
wasInterrupted = true;
}
}
else
lock.notifyAll(); // 走到这里说明线程池的状态已经发生变更,有可能已经解除锁定状态,故而提醒其他线程去抢占线程池的使用权
}
}
}
}
/**
* 获取公共线程池实例
* @return 公共线程池实例
*/
public static ForkJoinPool commonPool() {
// assert common != null : "static init error";
return common;
}
/**
* 执行任务并获取结果
* @param task 指定任务
* @return 任务的执行结果
*/
public <T> T invoke(ForkJoinTask<T> task) {
if (task == null)
throw new NullPointerException();
externalPush(task);
return task.join();
}
/**
* 提交任务
* @param task 指定任务
*/
public void execute(ForkJoinTask<?> task) {
if (task == null)
throw new NullPointerException();
externalPush(task);
}
/**
* 提交任务
* @param task 指定任务
*/
public void execute(Runnable task) {
if (task == null)
throw new NullPointerException();
ForkJoinTask<?> job;
if (task instanceof ForkJoinTask<?>) // avoid re-wrap
job = (ForkJoinTask<?>) task;
else
job = new ForkJoinTask.RunnableExecuteAction(task);
externalPush(job);
}
/**
* 提交任务
* @param task 指定任务
* @return 任务
*/
public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) {
if (task == null)
throw new NullPointerException();
externalPush(task);
return task;
}
/**
* 提交任务
* @param task 指定任务
* @param result 任务的执行结果
* @return 任务
*/
public <T> ForkJoinTask<T> submit(Runnable task, T result) {
ForkJoinTask<T> job = new ForkJoinTask.AdaptedRunnable<T>(task, result);
externalPush(job);
return job;
}
/**
* 提交任务
* @param task 指定任务
* @return 任务
*/
public ForkJoinTask<?> submit(Runnable task) {
if (task == null)
throw new NullPointerException();
ForkJoinTask<?> job;
if (task instanceof ForkJoinTask<?>) // avoid re-wrap
job = (ForkJoinTask<?>) task;
else
job = new ForkJoinTask.AdaptedRunnableAction(task);
externalPush(job);
return job;
}
/**
* 更新线程池的状态,实际上是将状态从 RSLOCK -> newRunState
* 能够调用该方法的前提是获得了线程池的使用权,即让线程池处于RSLOCK状态
* @param oldRunState 线程池的当前状态
* @param newRunState 线程池的新状态
*/
private void unlockRunState(int oldRunState, int newRunState) {
/**
* 通常情况下该判断语句的结果是false,说明线程池的状态未发生改变,所以进入到代码片段中手动将其更改(注意此时还未唤醒其他线程),并在最后将其他线程唤醒,因为此时线程池已经处于新的状态下
*/
if (!U.compareAndSwapInt(this, RUNSTATE, oldRunState, newRunState)) {
Object lock = stealCounter;
runState = newRunState; // 将其标识位RSIGNAL删除,并在后续唤醒其他所有线程
if (lock != null)
synchronized (lock) {
lock.notifyAll(); // 唤醒其他所有线程
}
}
}
/**
* 有条件关闭线程池
* 有条件:没有活跃的工作线程或任务都执行完毕
*/
public void shutdown() {
checkPermission();
tryTerminate(false, true);
}
/**
* 无条件关闭线程池,尝试取消所有任务
*/
public List<Runnable> shutdownNow() {
checkPermission();
tryTerminate(true, true);
return Collections.emptyList();
}
/**
* 线程池是否已终止
* @return 结果值
*/
public boolean isTerminated() {
return (runState & TERMINATED) != 0;
}
/**
* 线程池是否已关闭
* @return 结果值
*/
public boolean isTerminating() {
int rs = runState;
return (rs & STOP) != 0 && (rs & TERMINATED) == 0;
}
// 队列
static final class WorkQueue {
/**
* 队列的默认容量大小,必须是2的幂次方
*/
static final int INITIAL_QUEUE_CAPACITY = 1 << 13;
/**
* 队列的最大容量大小,必须是2的幂次方,至于为什么是2的幂次方是为了简化基于索引的操作
*/
static final int MAXIMUM_QUEUE_CAPACITY = 1 << 26;
/**
* 队列的运行状态
* < 0:表示队列处于空闲状态
* 奇数:表示队列处于运行中
* 偶数:表示队列处于忙碌中,正在执行任务
*/
volatile int scanState;
/**
* 关联上一个最新的空闲线程,虽然其值是ctl,但会通过 & 与SP_MASK计算获取上一个空闲线程,包括其版本计数、处于空闲状态、索引值
*/
int stackPred;
/**
* 窃取到任务的数量,源码中未看到何种情况下会为负数
*/
int nsteals;
/**
* 随机数
*/
int hint;
/**
* 通过 | 计算来保存队列的索引与模式信息,低16位表示索引,高16位表示模式
*
* 0000 0000 0000 0001 0000 0000 0000 0001
* | 高16位 || 低16位 |
*
* 索引:在上面中我们提到有内部队列与共享队列,这两种队列分别存放在奇数与偶数索引位置上
*
* 模式:该名词解释同上面的含义一致
*/
int config;
/**
* 队列的使用状态,不用过多纠结与scanState的区别,只要明白每个值的意思即可
* 1:队列处于锁定状态,其表明当多个任务同时提交到同一个队列时,只允许其中一个任务进行提交
* -1:队列处于终止,当工作线程启动失败或终止时,其对应的队列遭到废弃
*/
volatile int qlock;
/**
* 上面说到队列属于双端队列,该队列会被自身线程去处理,同时也会被其他线程窃取,那么为了减少多线程之间的资源竞争,故而规定这两种方式分别从两边开始获取任务
* 对于其他线程窃取来说,通常调用poll方法,当任务被窃取后索引自然会变更
* 所以该属性是记录下一个被窃取的任务的索引,默认情况下对于窃取来说,按照先进先出的顺序开始获取任务,即对于任务数组来说,该索引是呈现递增的趋势,即 base, base + 1, base + 2 ...
*/
volatile int base;
/**
* 对于自身线程去处理任务时,通常调用push/pop方法,所以该属性是记录下一个要存储的任务的索引,若要获取最后存储的任务则应该是 top - 1
* 默认情况下对于自身任务来说,按照先进后出的顺序开始获取任务,即对于任务数组来说,该索引呈现递减的趋势,即top - 1 , top -2 ...
* 当且仅当 base == top 时表示任务数组中的所有任务全部处理完毕
*/
int top;
/**
* 存储队列中的任务
*/
ForkJoinTask<?>[] array;
/**
* 当前线程池
*/
final ForkJoinPool pool;
/**
* 队列对应的工作线程,针对共享队列时该值为null
*/
final ForkJoinWorkerThread owner;
/**
* 引用指向当前工作线程,用于空闲队列的阻塞与唤醒
*/
volatile Thread parker;
/**
* 调用join方法的任务
*/
volatile ForkJoinTask<?> currentJoin;
/**
* 窃取到的任务
*/
volatile ForkJoinTask<?> currentSteal;
/**
* 初始化
*/
WorkQueue(ForkJoinPool pool, ForkJoinWorkerThread owner) {
this.pool = pool;
this.owner = owner;
base = top = INITIAL_QUEUE_CAPACITY >>> 1; //初始化时将两个索引置为数组的中心
}
/**
* 获取队列的索引
* 原本config & 0xffff就是其索引的值,但不太明白为什么还要除以2
* @return 队列的索引
*/
final int getPoolIndex() {
return (config & 0xffff) >>> 1;
}
/**
* 获取任务的个数,其结果只是个大约值
* @return 任务的个数
*/
final int queueSize() {
int n = base - top;
return (n >= 0) ? 0 : -n;
}
/**
* 判断任务数组是否为空
* @return 结果值
*/
final boolean isEmpty() {
ForkJoinTask<?>[] a; int n, m, s;
return ((n = base - (s = top)) >= 0 || (n == -1 && ((a = array) == null || (m = a.length - 1) < 0 || U.getObject(a, (long)((m & (s - 1)) << ASHIFT) + ABASE) == null)));
}
/**
* 添加任务
* 该方法通常由ForkJoinTask#fork中调用
* @param task 任务
*/
final void push(ForkJoinTask<?> task) {
ForkJoinTask<?>[] a; ForkJoinPool p;
int b = base, s = top, n;
if ((a = array) != null) {
int m = a.length - 1;
U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
U.putOrderedInt(this, QTOP, s + 1);
if ((n = s - b) <= 1) {
if ((p = pool) != null)
p.signalWork(p.workQueues, this);
}
else if (n >= m) // 想不明白什么情况下 n >= m,就算发生了扩容,但其base、top的值还是原来的
growArray();
}
}
/**
* 执行窃取到的任务及自身队列的所有任务
* 在执行自身队列的所有任务时,默认以LIFO的顺序开始获取任务并执行
* @param task 窃取到的任务
*/
final void runTask(ForkJoinTask<?> task) {
if (task != null) {
scanState &= ~SCANNING; // 使队列处于忙碌中,偶数数值,因为 ~SCANNING是个偶数,故而 & 后的结果仍然是偶数
/**
* 执行窃取到的任务 doExec -> exec -> compute 执行业务方法
* 若在业务方法中执行了ForkJoinTask#fork方法,接着调用WorkQueue#push,最终该任务落到了该队列上,也就是说窃取到的任务会落到窃取的队列上,这点和执行自身任务有所区别!!!
*/
(currentSteal = task).doExec();
U.putOrderedObject(this, QCURRENTSTEAL, null); // currentSteal = null
execLocalTasks(); // 自身线程执行队列中任务
ForkJoinWorkerThread thread = owner; // 获取自身线程
if (++nsteals < 0)
transferStealCount(pool);
scanState |= SCANNING; // 执行完自身队列的任务后,该去窃取其他线程的任务,所以更换成运行中状态
if (thread != null)
thread.afterTopLevelExec();
}
}
/**
* 自身线程执行队列中的任务
*/
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) { // 自身队列要以哪一个顺序开始获取任务并执行,若 config & FIFO_QUEUE == 0 成立的话,说明以先进后出的顺序(LIFO),则从top处开始获取任务,否则执行 pollAndExecAll
for (ForkJoinTask<?> t;;) {
if ((t = (ForkJoinTask<?>)U.getAndSetObject(a, ((m & s) << ASHIFT) + ABASE, null)) == null) // 从top呈现递减的趋势开始获取任务,若获取到的任务为null表明没有任务了
break;
U.putOrderedInt(this, QTOP, s); // 更新top的值
t.doExec(); // 执行任务,doExec -> exec -> compute 执行业务方法,若在业务方法中执行了ForkJoinTask#fork方法,接着调用WorkQueue#push,最终该任务落到了同一个队列上,可参看push方法
/**
* 对于双端队列来说,base 呈现递增的趋势,即 base, base + 1, base + 2 ,而 top呈现递减的趋势,即 top, top -1, top-2,
* 所以当base > top -1 时说明队列中的所有任务都执行完毕了,而当 base == top-1 时表明窃取线程与自身线程都落到了最后同一个任务上,但由于采用的是CAS,原子化特性保证最终只会有一个线程能获取到
*/
if (base - (s = top - 1) > 0)
break;
}
}
else
pollAndExecAll(); // 自身线程以FIFO(先进先出)顺序开始获取队列中的任务并执行
}
}
/**
* 自身线程以FIFO(先进先出)顺序开始获取队列中的任务并执行
*/
final void pollAndExecAll() {
for (ForkJoinTask<?> t; (t = poll()) != null;)
t.doExec();
}
/**
* 自身线程以FIFO(先进先出)顺序开始获取队列中的任务
* 注意:上面我们提到窃取线程也会从base处开始获取任务,而此方法也会从base,这无疑就加重了多线程之间的冲突,即在任务很多的情况下要频繁判断base的值是否发生了变化,所以在没必要的情况最好还是使用默认的LIFO顺序
* @return 获取的任务或null
*/
final ForkJoinTask<?> poll() {
ForkJoinTask<?>[] a; int b; ForkJoinTask<?> t;
while ((b = base) - top < 0 && (a = array) != null) {
int j = (((a.length - 1) & b) << ASHIFT) + ABASE;
t = (ForkJoinTask<?>)U.getObjectVolatile(a, j);
if (base == b) { // 判断base是否发生了改变,若改变的话就要重新在判断
if (t != null) {
if (U.compareAndSwapObject(a, j, t, null)) { // 再次判断是否能够获取到任务
base = b + 1;
return t;
}
}
else if (b + 1 == top) // 当base == top - 1表明执行到了最后一个任务,但到底被窃取了还是自身执行了呢? 因为 t = null,表明自身队列未能获取到最后一个任务
break;
}
}
return null;
}
/**
* 初始化任务数组或对其2倍大小的扩容
* @return 任务数组
*/
final ForkJoinTask<?>[] growArray() {
ForkJoinTask<?>[] oldA = array;
int size = oldA != null ? oldA.length << 1 : INITIAL_QUEUE_CAPACITY;
if (size > MAXIMUM_QUEUE_CAPACITY)
throw new RejectedExecutionException("Queue capacity exceeded");
int oldMask, t, b;
ForkJoinTask<?>[] a = array = new ForkJoinTask<?>[size];
/**
* 下面的代码片段表示任务数组要进行扩容
* 扩容无非就是将旧数组中未执行的任务移动到新数组中
* 而base、top的值并未发生改变
*/
if (oldA != null && (oldMask = oldA.length - 1) >= 0 && (t = top) - (b = base) > 0) {
int mask = size - 1;
do {
ForkJoinTask<?> x;
int oldj = ((b & oldMask) << ASHIFT) + ABASE;
int j = ((b & mask) << ASHIFT) + ABASE;
x = (ForkJoinTask<?>)U.getObjectVolatile(oldA, oldj);
if (x != null &&
U.compareAndSwapObject(oldA, oldj, x, null))
U.putObjectVolatile(a, j, x);
} while (++b != t);
}
return a;
}
/**
* 自身线程以LIFO(先进后出)顺序开始获取队列中的任务
* @return 任务或null
*/
final ForkJoinTask<?> pop() {
ForkJoinTask<?>[] a; ForkJoinTask<?> t; int m;
if ((a = array) != null && (m = a.length - 1) >= 0) {
for (int s; (s = top - 1) - base >= 0;) { // top -1 >= base 说明至少有一个任务
long j = ((m & s) << ASHIFT) + ABASE;
if ((t = (ForkJoinTask<?>)U.getObject(a, j)) == null)
break;
if (U.compareAndSwapObject(a, j, t, null)) {
U.putOrderedInt(this, QTOP, s);
return t;
}
}
}
return null;
}
/**
* 获取base索引上的任务
* 其实我感觉都没必要传入参数b了,直接拿base上的任务不就可以了吗..
* @param b 要求其值要等于base
* @return 任务或null
*/
final ForkJoinTask<?> pollAt(int b) {
ForkJoinTask<?> t; ForkJoinTask<?>[] a;
if ((a = array) != null) {
int j = (((a.length - 1) & b) << ASHIFT) + ABASE;
if ((t = (ForkJoinTask<?>)U.getObjectVolatile(a, j)) != null &&
base == b && U.compareAndSwapObject(a, j, t, null)) {
base = b + 1;
return t;
}
}
return null;
}
/**
* 按照指定模式获取自身队列的下一个任务
* FIFO(先进先出):poll
* LIFO(先进后出):pop
* @return 任务或null
*/
final ForkJoinTask<?> nextLocalTask() {
return (config & FIFO_QUEUE) == 0 ? pop() : poll();
}
/**
* 按照指定模式获取自身队列的下一个任务
* FIFO(先进先出):base
* LIFO(先进后出):top - 1
* @return 任务或null
*/
final ForkJoinTask<?> peek() {
ForkJoinTask<?>[] a = array; int m;
if (a == null || (m = a.length - 1) < 0)
return null;
int i = (config & FIFO_QUEUE) == 0 ? top - 1 : base;
int j = ((i & m) << ASHIFT) + ABASE;
return (ForkJoinTask<?>)U.getObjectVolatile(a, j);
}
/**
* top - 1索引上的任务是否移除成功(最后一个任务),要求入参t必须与top - 1上的任务的引用相同
* @param t 指定任务
* @return 结果值
*/
final boolean tryUnpush(ForkJoinTask<?> t) {
ForkJoinTask<?>[] a; int s;
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;
}
/**
* 取消任务数组中的所有任务,此取消只是改变了任务的状态
*/
final void cancelAll() {
ForkJoinTask<?> t;
if ((t = currentJoin) != null) {
currentJoin = null;
ForkJoinTask.cancelIgnoringExceptions(t);
}
if ((t = currentSteal) != null) {
currentSteal = null;
ForkJoinTask.cancelIgnoringExceptions(t);
}
while ((t = poll()) != null)
ForkJoinTask.cancelIgnoringExceptions(t);
}
/**
* 更新线程池中已窃取的任务数量
* @param p 线程池
*/
final void transferStealCount(ForkJoinPool p) {
AtomicLong sc;
if (p != null && (sc = p.stealCounter) != null) {
int s = nsteals;
nsteals = 0;
sc.getAndAdd((long)(s < 0 ? Integer.MAX_VALUE : s));
}
}
/**
* 执行指定任务并移除或移除已取消的任务
* 该方法会在join时被调用,而在调用join时自身线程没有处理自身队列的任务(因为在执行join方法可能会发生阻塞等待),所以该队列里的任务只能被其他线程窃取
* 移除的任务只允许是处于base或top-1上的任务,否则就会造成任务数组断断续续的出现null
* 当方法返回true时说明任务数组为空,表明任务被其他线程窃取了,那么这个时候该线程只能干等着,于其如此还不如帮忙去窃取其他线程的任务,哈哈哈,然后窃取一个任务执行完成后就去看看任务完成了没有
* @param task 指定任务
* @return true:任务数组为空 false:队列中存在任务或指定任务处理完毕
*/
final boolean tryRemoveAndExec(ForkJoinTask<?> task) {
ForkJoinTask<?>[] a; int m, s, b, n;
if ((a = array) != null && (m = a.length - 1) >= 0 && task != null) {
while ((n = (s = top) - (b = base)) > 0) { // top - base > 0 说明有任务存在
for (ForkJoinTask<?> t;;) { // 遍历任务数组,从(top -1) 到 base,执行指定任务并移除或移除已取消的任务,能移除的任务只允许是处于base或top-1上的任务,否则就会造成任务数组断断续续的出现null
long j = ((--s & m) << ASHIFT) + ABASE;
if ((t = (ForkJoinTask<?>)U.getObject(a, j)) == null) // 判断此时位置的任务是否为空,因为有可能早已被其他线程窃取了
/**
* 随着多次遍历s一直在发生改变,而当某个位置的任务为空时,其 s + 1 并不等于 top,这就说明了存在着任务
* 要出现true的情况是任务数组是空的
*/
return s + 1 == top;
else if (t == task) { // 尝试去执行任务并从任务数组中移除,而能移除的任务只允许是处于base或top-1上的任务,否则就会造成任务数组断断续续的出现null
boolean removed = false;
if (s + 1 == top) {
if (U.compareAndSwapObject(a, j, task, null)) {
U.putOrderedInt(this, QTOP, s);
removed = true;
}
}
else if (base == b) // 虽然这里将base位置上的任务更换成EmptyTask,但实际上什么都没做,个人觉得如此做不用再去修改base的值,不用让窃取线程在调用scan方法时重新遍历一次
removed = U.compareAndSwapObject(a, j, task, new EmptyTask());
if (removed)
task.doExec(); // 尝试执行任务,其方法会判断任务的状态
break;
}
else if (t.status < 0 && s + 1 == top) { // 能走到这里说明任务还未被处理,由于status < 0 有可能是正常处理(不能被取消)、未处理被取消了、处理中抛出异常这些情况,所以这里说明top -1 处的任务被取消了
if (U.compareAndSwapObject(a, j, t, null))
U.putOrderedInt(this, QTOP, s);
break;
}
if (--n == 0) // 队列数组中没有与之匹配的任务,比如直接调用join而没有调用fork,不过最好不要这样子做...
return false;
}
if (task.status < 0) // status < 0 表示任务处理结束,有可能是正常处理,也有可能是被取消了
return false;
}
}
return true;
}
}
运行流程
ForkJoinPool的代码又多又繁琐,看了第一遍之后再也不想看第二遍了,所以准备总结下提交任务的一个运行流程,如下图所示:

如若运行流程图不理解,可参看后面的使用示例或许能帮助其理解。
使用示例
为什么要写使用示例呢?因为笔者在工作中其实也没有遇到过使用ForkJoinPool的场景,此篇文章光是把源码看懂了一遍,实际上到头来还是很多人不知道怎么使用ForkJoinPool,当然了我也是如此,所以这里贴出了使用实例。
public class ForkJoinPoolTest {
public static void main(String[] args) throws Exception{
ForkJoinPool forkJoinPool = ForkJoinPool.commonPool();
CountTaskForkJoinTest countTaskForkJoinTest = new CountTaskForkJoinTest(0, 100); // 大任务
forkJoinPool.submit(countTaskForkJoinTest); // 提交任务
Long result = countTaskForkJoinTest.get(); // 获取结果
System.out.println(result);
}
}
/**
* RecursiveTask是ForkJoinTask的一个子类,其中还有RecursiveAction子类
* RecursiveTask在执行任务后提供了返回值
* RecursiveAction只负责执行
* 区分以上两个类的使用场景就很容易写了
*/
class CountTaskForkJoinTest extends RecursiveTask<Long> {
private static final long serialVersionUID = 1L;
private static final int threshold = 100;//临界值
private long start;
private long end;
public CountTaskForkJoinTest(long start, long end) {
this.start = start;
this.end = end;
}
/**
* 重写compute方法,判断是否将任务进行拆分计算
*/
@Override
protected Long compute() {
long sum = 0;
//判断是否是拆分完毕
boolean canCompute = (end - start) <= threshold;
if (canCompute) {
for (long i = start; i <= end; i++) {
sum += i;
}
} else {
// 将任务拆分成子任务
long middle = (start + end) / 2;
CountTaskForkJoinTest task1 = new CountTaskForkJoinTest(start, middle);
CountTaskForkJoinTest task2 = new CountTaskForkJoinTest(middle, end);
task1.fork();
task2.fork();
sum = task1.join() + task2.join();
}
return sum;
}
}
总结
ForkJoinPool适合任务执行时间小且任务数量多的场景,其核心思想是每个线程对应一个队列,当线程将自身队列的任务处理完毕后尝试去窃取其他队列的任务,而只有把窃取的任务执行完成了之后才能继续去窃取下一个,虽然代码内部中分为共享队列与内部队列,实际上共享队列只是单纯地存储任务并没有分配线程去处理,所以它的任务都是被一个一个地窃取,通过外部提交的任务会被放入到共享队列中,由于自身线程会去执行任务,其他线程也会窃取该队列的任务,为了降低线程之间的竞争,采用了双端队列,基于两个索引进行操作。源码中涉及到较多的计算,也较难理解,笔者断断续续差不多花费了2-3个月时间才算告一段落,事后觉得计算部分只要能明白意思即可,不用做太多的深入,感觉意义也不是很大,其核心概念理解就差不多了。顺便在提一点,源码提到了公共线程池,为了方便开发者的使用,可以在不理解ForkJoinPool的基础上直接使用公共线程池,其最大的特性就是不会随着shutdown/shutdownNow方法的调用而关闭,只会等到程序结束了才关闭,对于同一个程序来说,始终只有一个公共线程池!
重点关注
工作窃取 双端队列 使用示例 公共线程池
浙公网安备 33010602011771号