/**
* 运行任务直到isQuiescent()。我们利用活动计数ctl维护,但不是在找不到任务时阻塞,
* 而是重新扫描,直到其他所有工作者也找不到任务。
* Runs tasks until {@code isQuiescent()}. We piggyback on
* active count ctl maintenance, but rather than blocking
* when tasks cannot be found, we rescan until all others cannot
* find tasks either.
*/
// 判断工作线程的队列中是否还有任务,若有任务,帮助执行,若没有任务,等待所有的线程把任务执行完成后(活动线程数为0)返回
// ForkJoinThread线程才能执行这个方法, w -> 执行这个方法的工作线程的队列
final void helpQuiescePool(WorkQueue w) {
ForkJoinTask<?> ps = w.currentSteal;
for (boolean active = true;;) {
long c; WorkQueue q; ForkJoinTask<?> t; int b;
// 获取队列中的下一个任务 (根据mode 模式按顺序获取),先执行完自己工作线程的所有任务
while ((t = w.nextLocalTask()) != null)
// 执行任务
t.doExec();
// 查找非空的工作线程所拥有的队列
if ((q = findNonEmptyStealQueue()) != null) {
if (!active) { // re-establish active count 重新建立活动线程数
active = true;
// AC_MASK 高16位为: 1111 1111 1111 1111,低48位为0
// AC_UNIT = 1L << 48 , 修改ctl,活动线程数 + 1
do {} while (!U.compareAndSwapLong
(this, CTL, c = ctl,
((c & ~AC_MASK) |
((c & AC_MASK) + AC_UNIT))));
}
// q.pollAt(b) 获取base索引位置的任务
if ((b = q.base) - q.top < 0 && (t = q.pollAt(b)) != null)
// runTask()执行顶级任务和执行后剩余的任何本地任务
w.runTask(t);
// 只窃取一个任务,执行完就继续 for循环了
}
// ----- 没有非空的工作线程所拥有的队列 ------
// 减少活动计数而不排队
else if (active) { // decrement active count without queuing
// AC_MASK 高16位为: 1111 1111 1111 1111,低48位为0
// AC_UNIT = 1L << 48 , 计算结果: 低48位保持不变,高16位减1
long nc = ((c = ctl) & ~AC_MASK) | ((c & AC_MASK) - AC_UNIT);
// AC_SHIFT = 48, 判断活动线程数是否为 0,即只剩下一个活动线程了
if ((int)(nc >> AC_SHIFT) + parallelism == 0)
break; // bypass decrement-then-increment
// 修改ctl的值 (把活动线程数 - 1的原因是,可能会有多个线程同时执行这个方法,但是不知道具体几个线程,因此
// 扣掉自身计算活动线程数,即在执行这个方法的活动线程数,然后等活动线程数为0后,即可判断队列所有线程的任务
// 都已经执行完成了,工作线程执行这个方法时,eventCount不可能是 -1,因为 -1时工作线程不能执行任务)
if (U.compareAndSwapLong(this, CTL, c, nc))
// CAS成功, active = false
active = false;
}
// AC_SHIFT = 48, AC_MASK 高16位为: 1111 1111 1111 1111,低48位为0
// (int)((c = ctl) >> AC_SHIFT) + parallelism <= 0 说明活动线程为0 了
// 修改 ctl,活动线程数 + 1
else if ((int)((c = ctl) >> AC_SHIFT) + parallelism <= 0 &&
U.compareAndSwapLong
(this, CTL, c, ((c & ~AC_MASK) |
((c & AC_MASK) + AC_UNIT))))
break;
}
}
/**
* 获取并移除给定工作线程的本地任务或窃取的任务。
* Gets and removes a local or stolen task for the given worker.
*
* @return a task, if available
*/
final ForkJoinTask<?> nextTaskFor(WorkQueue w) {
for (ForkJoinTask<?> t;;) {
WorkQueue q; int b;
// 按模式指定的顺序获取并删除下一个任务
if ((t = w.nextLocalTask()) != null)
return t;
// 查找非空的工作队列
if ((q = findNonEmptyStealQueue()) == null)
return null;
// 从工作队列中取出base索引位置的任务 (如果因为竞争取出任务失败,会继续循环)
if ((b = q.base) - q.top < 0 && (t = q.pollAt(b)) != null)
return t;
}
}
/**
* 当程序员、框架、工具或语言很少或完全不了解任务粒度时,返回用于任务划分的廉价启发式向导。
* Returns a cheap heuristic guide for task partitioning when
* programmers, frameworks, tools, or languages have little or no
* idea about task granularity. In essence by offering this
* 本质上,通过提供这种方法,我们只询问用户在开销与预期吞吐量及其差异之间的权衡,而不是如何精细地划分任务。
* method, we ask users only about tradeoffs in overhead vs
* expected throughput and its variance, rather than how finely to
* partition tasks.
*
* 在稳定状态的严格(树形结构)计算中,每个线程都可以窃取足够的任务,以使其他线程保持活动状态。
* In a steady state strict (tree-structured) computation, each
* thread makes available for stealing enough tasks for other
* threads to remain active. Inductively, if all threads play by
* 归纳起来,如果所有线程都遵循相同的规则,那么每个线程应该只提供固定数量的任务。
* the same rules, each thread should make available only a
* constant number of tasks.
*
* 有用的最小常数是1。
* The minimum useful constant is just 1. But using a value of 1
* 但是使用1的值需要在每次偷取时立即补充以维持足够的任务,这是不可行的。
* would require immediate replenishment upon each steal to
* maintain enough tasks, which is infeasible. Further,
* 此外,所提供任务的分割/粒度应该最小化偷取率,这通常意味着接近计算树顶部的线程
* 应该比接近计算树底部的线程生成更多。
* partitionings/granularities of offered tasks should minimize
* steal rates, which in general means that threads nearer the top
* of computation tree should generate more than those nearer the
* 在完全稳定状态下,每个线程的计算树大致处于相同的级别。
* bottom. In perfect steady state, each thread is at
* approximately the same level of computation tree. However,
* 然而,产生额外的任务会摊销进度的不确定性和扩散的假设。
* producing extra tasks amortizes the uncertainty of progress and
* diffusion assumptions.
*
* 因此,用户将希望使用大于1的值(但不要太大),以消除暂时的短缺和预防不平衡的进展;
* 以抵消额外任务开销的成本。
* So, users will want to use values larger (but not much larger)
* than 1 to both smooth over transient shortages and hedge
* against uneven progress; as traded off against the cost of
* extra task overhead. We leave the user to pick a threshold
* 我们让用户选择一个阈值来与此调用的结果进行比较,以指导决策,但建议使用诸如3这样的值。
* value to compare with the results of this call to guide
* decisions, but recommend values such as 3.
*
* 当所有线程都处于活动状态时,严格地在局部估计剩余通常是可以的。
* When all threads are active, it is on average OK to estimate
* surplus strictly locally. In steady-state, if one thread is
* 在稳定状态下,如果一个线程保持两个剩余任务,那么其他线程也会保持。
* maintaining say 2 surplus tasks, then so are others. So we can
* 所以我们可以使用估计的队列长度。
* just use estimated queue length. However, this strategy alone
* 然而,在某些非稳态条件下(斜坡上升、斜坡下降、其他档位),这种策略本身就会导致严重的错误估计。
* leads to serious mis-estimates in some non-steady-state
* conditions (ramp-up, ramp-down, other stalls). We can detect
* 我们可以通过进一步考虑“空闲”线程的数量来检测其中的许多,已知这些线程没有任何排队的任务,
* 因此可以通过(#idle/#active)线程的因素进行补偿。
* many of these by further considering the number of "idle"
* threads, that are known to have zero queued tasks, so
* compensate by a factor of (#idle/#active) threads.
*
* 注意:在当前的信令方案下,#busy workers与#active workers的近似值不是很好,需要改进。
* Note: The approximation of #busy workers as #active workers is
* not very good under current signalling scheme, and should be
* improved.
*/
static int getSurplusQueuedTaskCount() {
Thread t; ForkJoinWorkerThread wt; ForkJoinPool pool; WorkQueue q;
if (((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)) {
int p = (pool = (wt = (ForkJoinWorkerThread)t).pool).parallelism;
int n = (q = wt.workQueue).top - q.base;
// AC_SHIFT = 48, 计算当前活动的线程数量
int a = (int)(pool.ctl >> AC_SHIFT) + p;
return n - (a > (p >>>= 1) ? 0 : // 当前活动线程数 > 1/2 parallelism
a > (p >>>= 1) ? 1 : // 当前活动线程数 > 1/4 parallelism
a > (p >>>= 1) ? 2 : // 当前活动线程数 > 1/8 parallelism
a > (p >>>= 1) ? 4 : // 当前活动线程数 > 1/16 parallelism
8);
}
// 如果当前线程不是工作线程,返回0
return 0;
}
// Termination
/**
* 可能启动 和/或 完成终止。调用者通过工作队列触发终止运行三遍:
* Possibly initiates and/or completes termination. The caller
* triggering termination runs three passes through workQueues:
* (0)设置终止状态,紧接着队列workers被唤醒; (1)取消所有任务;
* (2)中断滞后的线程(可能在外部任务中,但也可能在join阻塞)。
* (0) Setting termination status, followed by wakeups of queued
* workers; (1) cancelling all tasks; (2) interrupting lagging
* threads (likely in external tasks, but possibly also blocked in
* 由于潜在的滞后线程创建,每一次循环都重复前面的步骤。
* joins). Each pass repeats previous steps because of potential
* lagging thread creation.
*
* 如果true,无条件终止,否则只有没有工作和没有活跃的worker时终止
* @param now if true, unconditionally terminate, else only
* if no work and no active workers
* 如果为true,则在下一次可能时启用shutdown
* @param enable if true, enable shutdown when next possible
* 如果正在终止/已经终止,返回true
* @return true if now terminating or terminated
*/
// 如果正在终止/已经终止,返回true
private boolean tryTerminate(boolean now, boolean enable) {
int ps;
// common 不能终止
if (this == common) // cannot shut down
return false;
if ((ps = plock) >= 0) { // enable by setting plock
// 如果enable = false,那么当前池状态未终止,则返回false
if (!enable)
return false;
// ----- 修改 plock 最高位的值为1,表示池正在进行终止 -----
// PL_LOCK = 0010, (ps & PL_LOCK) != 0 说明 plock锁已经被获取了
// 或者 使用CAS获取锁失败,则调用 acquirePlock()获取锁
if ((ps & PL_LOCK) != 0 ||
!U.compareAndSwapInt(this, PLOCK, ps, ps += PL_LOCK))
// 获取锁,并返回获取锁后 plock的值
ps = acquirePlock();
// SHUTDOWN = 1 << 31,ps + 2 即释放锁,然后最高位设置为1
// 计算释放锁后 plock的值,并且最高位设置为1,表示池已经终止
int nps = ((ps + PL_LOCK) & ~SHUTDOWN) | SHUTDOWN;
// 修改plcok,表示池已终止, CAS失败说明有线程正在等待获取plock锁
if (!U.compareAndSwapInt(this, PLOCK, ps, nps))
// 1、对plock进行赋值,释放锁 2、唤醒所有等待的线程
releasePlock(nps);
}
for (long c;;) {
// STOP_BIT = 1L << 31.
// ((c = ctl) & STOP_BIT) != 0 说明ctl第32位的值已经是1了 (已经正在终止)
if (((c = ctl) & STOP_BIT) != 0) { // already terminating 已经在执行终止
// TC_SHIFT = 32.
// (short)(c >>> TC_SHIFT) 表示总workers数减去parallelism。
// (short)(c >>> TC_SHIFT) + parallelism <= 0 说明没有worker了
if ((short)(c >>> TC_SHIFT) + parallelism <= 0) {
synchronized (this) {
// 当没有workers时唤醒所有线程。(因为可能有线程正在调用awaitTermination()方法进行等待)
notifyAll(); // signal when 0 workers
}
}
return true;
}
// 检查是否空闲 且 没有任务
if (!now) { // check if idle & no tasks
WorkQueue[] ws; WorkQueue w;
// AC_SHIFT = 48; (int)(c >> AC_SHIFT)表示: 活动运行workers的数量减去parallelism
// (int)(c >> AC_SHIFT) + parallelism > 0 说明还有活动的线程
if ((int)(c >> AC_SHIFT) + parallelism > 0)
// 当now = false,而此时还有活动的线程时,不能终止池
return false;
if ((ws = workQueues) != null) {
// 遍历workQueues
for (int i = 0; i < ws.length; ++i) {
// (i & 1) != 0 说明是奇数索引 (奇数索引位置是工作线程持有的workQueue)
// w.eventCount >= 0 说明线程还没有终止 (scan()方法先修改活动线程数
// 再修改evenCount,已经没有活动线程的时候,eventCount的值可能还 >= 0,
// 这种情况下此线程可能还处于活动状态)
if ((w = ws[i]) != null &&
(!w.isEmpty() || // w不为空 且 w的队列中还有任务
((i & 1) != 0 && w.eventCount >= 0))) {
// 前面已经判断过池中没有活动的线程了,所以调用 signalWork(ws, w) 通知/注册
// worker 执行任务 (需要把队列中的任务执行完)
signalWork(ws, w);
return false;
}
}
}
}
// ------ 池立即终止 (now = true), 或者池满足了终止条件 -----
// STOP_BIT = 1L << 31, 使用CAS修改ctl第32 位的值为1
if (U.compareAndSwapLong(this, CTL, c, c | STOP_BIT)) {
// (0)设置终止状态,紧接着队列workers被唤醒;
// (1)取消所有任务;
// (2)中断滞后的线程(可能在外部任务中,但也可能在join阻塞)。
// 由于潜在的滞后线程创建,每一次循环都重复前面的步骤。
for (int pass = 0; pass < 3; ++pass) {
WorkQueue[] ws; WorkQueue w; Thread wt;
if ((ws = workQueues) != null) {
int n = ws.length;
for (int i = 0; i < n; ++i) {
if ((w = ws[i]) != null) {
// 设置 qlock = -1,表示该workQueue已经终止了
w.qlock = -1;
// 取消所有任务
if (pass > 0)
4 ForkJoinPool 源码注释2
最新推荐文章于 2024-08-06 01:51:54 发布
本文详细介绍了ForkJoinPool的工作原理,包括任务的执行、并行度调整、任务调度、线程协作、终止策略以及常见方法的使用。重点分析了ForkJoinPool如何通过工作窃取算法实现高效的并行计算,并解释了其内部工作线程的管理和任务处理流程。此外,还讨论了如何在ForkJoinPool中实现任务的有序关闭和监控池的状态。
摘要由CSDN通过智能技术生成