ForkJoinPool
一 . 推理
1. 思考
(1) 思考一 :
ForkJoinPool 出现的原因?为什么不用ThreadPoolExecutor?
(2) 思考二 :
当待执行的任务,执行前提是完成其他任务(链式任务,该任务的前提还有子任务)该如何处理?
(3) 思考三 :
在ThreadPoolExecutor中的上述任务所处线程被阻塞了,会出现什么情况?
2. 思考图
3. 问题 :
- 如果使用固有的ThreadPoolExecutor及衍生类
- 其一,会使工作量变得巨大耗费性能
- 其二,需要业务手动维护任务之间的衔接(子任务线程的创建等或存入任务队列),及其他处理
- 其三,在子任务中完成工作需要创建线程完成前置任务,会占用固有线程数,如果线程池线程数与任务队列已经满载,即子任务无法进行,会面临无限期阻塞
二 . 架构
1. 理解图
2. 缺省版关键代码分析
(1) . 步骤一 : 调用ForkJoinPool
通过ForkJoinPool执行最简单的普通任务
ForkJoinPool forkJoinPool = new ForkJoinPool();
forkJoinPool.submit(()->{System.out.println("Hello Word");});
forkJoinPool.shutdown();
try {
forkJoinPool.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
(2) . 步骤二: submit()方法
将任务包装成ForkJoinPool 特有的任务形式,然后交由externalPush()提交任务到所属队列
public ForkJoinTask<?> submit(Runnable task) {
ForkJoinTask<?> job;
if (task instanceof ForkJoinTask<?>)
job = (ForkJoinTask<?>) task;
else
job = new ForkJoinTask.AdaptedRunnableAction(task);
externalPush(job);
return job;
}
(3) . 步骤三: externalPush()方法
由于首次执行,workQueues为null,即if条件不成立,所以直接执行最后一步externalSubmit(task);
final void externalPush(ForkJoinTask<?> task) { WorkQueue[] ws; WorkQueue q; int m; int r = ThreadLocalRandom.getProbe(); //获取与线程绑定的线程安全的随机数 int rs = runState; //线程池当前运行状态 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); return; } U.compareAndSwapInt(q, QLOCK, 1, 0); } externalSubmit(task); }
- 简化后
final void externalPush(ForkJoinTask<?> task) {
externalSubmit(task);
}
(4) . 步骤四 : externalSubmit() 方法
真正执行外部提交得任务得方法
(4.1) 简化版,方法概要
private void externalSubmit(ForkJoinTask<?> task) {
for (;;) {
//[1.0]如果线程池已经关闭
if ((rs = runState) < 0) {...}
//[2.0]如果线程池还没有初始化
else if ((rs & STARTED) == 0 ||
((ws = workQueues) == null || (m = ws.length - 1) < 0)) {...}
//[3.0]如果线程池正在运行
else if ((q = ws[k = r & m & SQMASK]) != null) {...}
//[4.0]如果找到的这个workQueues没有被创建,则创建
else if (((rs = runState) & RSLOCK) == 0) {...}
//[5.0]发生竞争时,让当前线程选取其他的workQueues来重试
else
move = true;
if (move)//重新获取下一个不同得随机数
r = ThreadLocalRandom.advanceProbe(r);
}
}
}
(4.2) 完整版,方法详解
private void externalSubmit(ForkJoinTask<?> task) {
//------------------取随机数-------------------------------------
int r;
if ((r = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit();
r = ThreadLocalRandom.getProbe();
}
//------------------死循环--------------------------------------
for (;;) {
WorkQueue[] ws; WorkQueue q; int rs, m, k;
boolean move = false;
if ((rs = runState) < 0) { //-----------------------------判断线程池是否已经关闭
tryTerminate(false, false);
throw new RejectedExecutionException();
}
else if ((rs & STARTED) == 0 || //--------STARTED 状态位尚未置位(状态位初始化为1),需要初始化
((ws = workQueues) == null || (m = ws.length - 1) < 0)) { // -------workQueues,需要初始化
//****************如果if条件不成立,但是由于||运算执行了所有的条件语句,使得ws与m变量已经初始化
int ns = 0;
rs = lockRunState(); //上锁,如果没上锁,则等待
try {
if ((rs & STARTED) == 0) { //------防止多线程操作,已经被初始化,所以再次判断STARTED状态,防止无效CAS
U.compareAndSwapObject(this, STEALCOUNTER, null,
new AtomicLong());
//并行度,即线程数量,大小最小为4 (当传入为1时得4)
int p = config & SMASK;
int n = (p > 1) ? p - 1 : 1;
//传入得数取2得倍数
n |= n >>> 1; n |= n >>> 2; n |= n >>> 4;
n |= n >>> 8; n |= n >>> 16; n = (n + 1) << 1;
//初始化工作队列(偶数为外部提交队列,奇数为工作窃取队列)
workQueues = new WorkQueue[n];
//初始化队列成功,将状态变为STARTED
ns = STARTED;
}
} finally {
//执行完成,释放锁
unlockRunState(rs, (rs & ~RSLOCK) | ns);
}
}
// 在上一个分支执行初始化之后,
// 第二次循环将会到达这里。
// 由于全局队列workQueues种存储了两种队列:外部提交队列、内部工作队列(任务窃取队列)
// 那么这时,应该去找到本线程对应任务应该存储在哪个外部提交队列里
// 则通过上面获取的随机种子r,来找到对应任务应该放在哪里
// SQMASK = 1111110,所以由SQMASK的前面的1来限定长度,末尾的0来表明,外部提交队列一定在偶数位
else if ((q = ws[k = r & m & SQMASK]) != null) {
// 由于当前提交队列是外部提交队列,那么一定会有多线程共同操作,那么为了保证并发安全,那么这里需要上锁,也即对当前提交队列进行锁定
if (q.qlock == 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) {
// 取提交队列的保存任务的数组array
ForkJoinTask<?>[] a = q.array;
int s = q.top;
boolean submitted = false;
try {
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);
//栈顶指针偏移一位
U.putOrderedInt(q, QTOP, s + 1);
submitted = true;
}
} finally {
U.compareAndSwapInt(q, QLOCK, 1, 0);
}
// 如果任务添加成功
if (submitted) {
//没有工作线程,创建工作线程并执行
signalWork(ws, q);
return;
}
}
// 由于当前线程无法获取初始计算的提交队列的锁,
// 那么这时发生了线程竞争,那么设置move标志位,
// 让线程在下一次循环的时候,重新计算随机数,让它寻找另外的队列。
move = true;
}
// 如果找到的这个任务队列没有被创建,则创建,但是,这里的RSLOCK的判断,在于,当没有别的线程持有RSLOCK的时候,才会进入。这是由于RSLOCK主管,runstate,可能有别的线程把状态改了,根本不需要再继续work了
else if (((rs = runState) & RSLOCK) == 0) {
// 创建外部提交队列,由于ForkJoinWorkerThread 内部线程为null,所以为外部提交队列
q = new WorkQueue(this, null);
// r为什么保存为hint,r是随机数,通过r找到当前外部提交队列,处于WQS的索引下标
q.hint = r;
// SHARED_QUEUE = 1 << 31;这里就是将整形int的符号位置1,所以为负数,SHARED_QUEUE表明当前队列是共享队列(外部提交队列)
q.config = k | SHARED_QUEUE;
// 由于当前任务队列并没有进行扫描任务,所以扫描状态位无效状态INACTIVE
q.scanState = INACTIVE;
rs = lockRunState();
// 确保线程池处于运行状态
// 由于可能两个线程同时进来操作,只有一个线程持有锁,那么只允许一个线程放创建的队列,但是这里需要注意的是:可能会有多个线程创建了WorkQueue,但是只有一个能成功
if (rs > 0 && (ws = workQueues) != null &&
k < ws.length && ws[k] == null){
//将其放入全局任务队列种
ws[k] = q;
}
//解锁
unlockRunState(rs, rs & ~RSLOCK);
}
else
move = true;
if (move)
r = ThreadLocalRandom.advanceProbe(r);
}
}
- 任务处理之 fork()方法
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;
}
- 任务存储之 WorkQueue类
@sun.misc.Contended
static final class WorkQueue {
static final int INITIAL_QUEUE_CAPACITY = 1 << 13;
static final int MAXIMUM_QUEUE_CAPACITY = 1 << 26; // 64M
volatile int scanState; // versioned, <0: inactive; odd:scanning
int stackPred; // pool stack (ctl) predecessor
int nsteals; // number of steals
int hint; // randomization and stealer index hint
int config; // pool index and mode
volatile int qlock; // 1: locked, < 0: terminate; else 0
volatile int base; // index of next slot for poll
int top; // index of next slot for push
ForkJoinTask<?>[] array; // the elements (initially unallocated)
final ForkJoinPool pool; // the containing pool (may be null)
final ForkJoinWorkerThread owner; // owning thread or null if shared
volatile Thread parker; // == owner during call to park; else null
volatile ForkJoinTask<?> currentJoin; // task being joined in awaitJoin
volatile ForkJoinTask<?> currentSteal; // mainly used by helpStealer
WorkQueue(ForkJoinPool pool, ForkJoinWorkerThread owner) {
this.pool = pool;
this.owner = owner;
// 初始化队列base与top
base = top = INITIAL_QUEUE_CAPACITY >>> 1;
}
}
@sun.misc.Contended 是 Java 8 新增的一个注解,对某事物加上该注解则表示该事物会单独占用一个缓存行(Cache Line)。
未完待续