小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
接着聊ForkJoinPool,本篇就来看看其源码。
ForkJoinTask任务状态
/** The run status of this task */
volatile int status; // accessed directly by pool and workers
static final int DONE_MASK = 0xf0000000; // mask out non-completion bits
static final int NORMAL = 0xf0000000; // must be negative
static final int CANCELLED = 0xc0000000; // must be < NORMAL
static final int EXCEPTIONAL = 0x80000000; // must be < CANCELLED
static final int SIGNAL = 0x00010000; // must be >= 1 << 16
static final int SMASK = 0x0000ffff; // short bits for tags
statue为ForkJoinTask的状态,初始值为0,标识正则处理任务状态;NORMAL:正常状态,标识任务正常结束;CANCELLED:标识任务被取消;EXCEPTIONAL:标识任务执行异常;SIGNAL: 通知状态,有其他任务依赖当前任务,任务结束前,通知其他任务join当前任务的结果。
ForkJoinTask
ForkJoinTask是Futrue接口的子类,作用就是根据任务的分解实现,将任务进行拆分,以及等待子任务的执行结果合并成父任务的结果。ForkJoinTask内部存在一个整数类型的成员status,该成员高16位记录任务的执行状态,如:如NORMAL、CANCELLED或EXCEPTIONAL,低16位预留用于记录用户自定义的任务标签。源码如下:
public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
private static final ExceptionNode[] exceptionTable;
private static final ReentrantLock exceptionTableLock;
private static final ReferenceQueue<Object> exceptionTableRefQueue;
//固定容量的exceptionTable(代表数组长度为32,下标存储链表头节点)
private static final int EXCEPTION_MAP_CAPACITY = 32;
static final class ExceptionNode extends WeakReference<ForkJoinTask<?>> {
final Throwable ex;
ExceptionNode next;
final long thrower; // 抛出异常的线程id
final int hashCode; // 在弱引用消失之前存储hashCode
ExceptionNode(ForkJoinTask<?> task, Throwable ex, ExceptionNode next) {
// //在ForkJoinTask被GC回收之后,会将该节点加入队列exceptionTableRefQueue
super(task, exceptionTableRefQueue);
this.ex = ex;
this.next = next;
this.thrower = Thread.currentThread().getId();
this.hashCode = System.identityHashCode(task);
}
}
// 任务执行完成后返回结果,未完成返回null
public abstract V getRawResult();
// 强制性的给定返回结果
protected abstract void setRawResult(V value);
// 如果执行过程抛出异常则记录捕获的异常并更改任务状态为EXCEPTIONAL
// 如果执行正常结束,设置任务状态为NORMAL正常结束状态
// 如果当前是子任务,设置为SIGNAL状态并通知其他需要join该任务的线程
protected abstract boolean exec();
// 阻塞等待任务执行结果
public final V get();
// 在给定时间内等待返回结果,超出给定时间则中断线程
public final V get(long timeout, TimeUnit unit);
// 阻塞非工作线程直至任务结束或者中断(该过程可能会发生窃取动作),返回任务的status值
private int externalInterruptibleAwaitDone();
// 尝试取消任务,成功返回true,反之false
public boolean cancel(boolean mayInterruptIfRunning);
// 判断任务是否已执行结束
public final boolean isDone();
// 判断任务是否被取消
public final boolean isCancelled();
// 执行任务
final int doExec();
// 修改任务状态
private int setcompletion (int completion);
// 取消任务
public boolean cancel(boolean mayInterruptIfRunning);
// 将新创建的子任务放入当前线程的工作队列
public final ForkJoinTask<V> fork();
public final V join();
private int doJoin();
// 执行任务,正常结束则返回结果,异常结束则报告异常
public final V invoke();
private int doInvoke();
// 阻塞线程直至任务执行结束,如果未执行完成,外部线程尝试帮助执行
private int externalAwaitDone();
public static void invokeAll(ForkJoinTask<?> t1, ForkJoinTask<?> t2);
public static void invokeAll(ForkJoinTask<?>... tasks);
public static <T extends ForkJoinTask<?>>
Collection<T> invokeAll(Collection<T> tasks);
// 记录异常信息以及设置任务状态
final int recordExceptionalCompletion(Throwable ex);
private void clearExceptionalCompletion();
private static void expungeStaleExceptions();
private Throwable getThrowableException();
}
ForkJoinTask内部成员包括表示任务状态的status,其他的成员则都是跟任务异常信息记录相关的。
fork()、join()
ForkJoinTask的主要方法有执行方法fork(),join(),执行任务方法invoke系列。
// fork方法
public final ForkJoinTask<V> fork() {
Thread t;
//如果线程类型为ForkJoinWorkerThread,则将任务推入workQueue进行处理,
//否则,交由ForkJoinPool的common线程池进行处理
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
ForkJoinPool.common.externalPush(this);
return this;
}
// join方法
public final V join() {
int s;
//调用doJoin()进行任务的执行,若任务结果为非正常完成,则根据状态抛出不同的异常,
//如若状态为CANCELLED,则抛出CancellationException(),异常;
//若状态为EXCEPTIONAL,则抛出包装后的异常
if ((s = doJoin() & DONE_MASK) != NORMAL)
reportException(s);
return getRawResult();
}
// doJoin方法
private int doJoin() {
int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
// status<0则直接返回status值
return (s = status) < 0 ? s :
// 若执行任务的当前线程类型为ForkJoinWorkerThread,且将任务从线程的工作队列中移除成功则调用doExec()执行任务,若任务执行状态为正常结束,则返回状态,否则awaitJoin()等待任务结束。
((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
(w = (wt = (ForkJoinWorkerThread)t).workQueue).
tryUnpush(this) && (s = doExec()) < 0 ? s :
wt.pool.awaitJoin(w, this, 0L) :
// 不是则调用externalAwaitDone()等待任务执行完成
externalAwaitDone();
}
fork方法处理过程:先判断当前线程是否为池中的工作线程类型 是:将当前任务压入当前线程的任务队列中;不是:将当前任务压入common池中某个工作线程的任务队列中。
doJoin&join方法判断有点多: 先判断任务状态status是否小于0:
小于:任务正常完成,返回status值
不小于:判断当前线程是否为当前线程类型为ForkJoinWorkerThread:
是:取出线程任务队列的当前task执行,判断执行是否结束:
结束:返回执行结束的status值
未结束:调用awaitJoin方法等待任务结束
不是:调用externalAwaitDone()方法等待任务执行完成
invoke源码
再看一下invoke源码,ForkJoinPool线程池执行。提交任务的方式有三种:invoke()、execute()以及submit(),源码如下:
public final V invoke() {
int s;
//执行任务并返回状态,处理同doJoin()类似
if ((s = doInvoke() & DONE_MASK) != NORMAL)
reportException(s);
return getRawResult();
}
private int doInvoke() {
int s; Thread t; ForkJoinWorkerThread wt;
//执行任务并获取任务状态,状态<0表示正常完成,否则等待任务完成
return (s = doExec()) < 0 ? s :
((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
(wt = (ForkJoinWorkerThread)t).pool.
awaitJoin(wt.workQueue, this, 0L) :
externalAwaitDone();
}
//处理两个任务
public static void invokeAll(ForkJoinTask<?> t1, ForkJoinTask<?> t2) {
int s1, s2;
//提交任务t2,交由线程池执行
t2.fork();
//执行任务t1并获取直接结果的任务状态
if ((s1 = t1.doInvoke() & DONE_MASK) != NORMAL)
t1.reportException(s1);
//获取任务t2的直接结果状态
if ((s2 = t2.doJoin() & DONE_MASK) != NORMAL)
t2.reportException(s2);
}
//处理多个任务
public static void invokeAll(ForkJoinTask<?>... tasks) {
Throwable ex = null;
int last = tasks.length - 1;
for (int i = last; i >= 0; --i) {
ForkJoinTask<?> t = tasks[i];
//任务为空则跑NPE异常
if (t == null) {
if (ex == null)
ex = new NullPointerException();
}
//非最后一个任务,则推入线程池执行
else if (i != 0)
t.fork();
//最后一个任务直接调用doInvoke()执行
else if (t.doInvoke() < NORMAL && ex == null)
ex = t.getException();
}
//遍历任务,获取任务执行结果
for (int i = 1; i <= last; ++i) {
ForkJoinTask<?> t = tasks[i];
if (t != null) {
//若有某个任务执行有异常,则取消所有任务
if (ex != null)
t.cancel(false);
//获取任务执行结果,若结果非正常结束,获取异常结果
else if (t.doJoin() < NORMAL)
ex = t.getException();
}
}
if (ex != null)
rethrow(ex);
}
WorkQueue源码
WorkQueue为ForkJoinPool的工作队列,其封装提交的任务ForkJoinTask、线程池ForkJoinPool、执行线程ForkJoinWorkerThread、及其他任务相关数据等。每个执行者ForkJoinWorkerThread对象中存在各自的工作队列,ForkJoinTask被存储在工作队列中,而ForkJoinPool使用一个WorkQueue数组管理协调所有执行者线程的队列。源码如下:
volatile int scanState;
int stackPred;
int nsteals;
int hint;
int config;
volatile int qlock;
volatile int base;
int top;
ForkJoinTask<?>[] array;
final ForkJoinPool pool;
final ForkJoinWorkerThread owner;
volatile Thread parker;
volatile ForkJoinTask<?> currentJoin;
volatile ForkJoinTask<?> currentSteal;
scanState:如果WorkQueue没有属于自己的owner(下标为偶数的都没有),该值为 inactive 也就是一个负数;如果有自己的owner,该值的初始值为其在WorkQueue[]数组中的下标,也肯定是个奇数;如果这个值,变成了偶数,说明该队列所属的Thread正在执行Task。
stackPred:前一个栈顶的ctl值,由此构成一个栈;
int nsteals:窃取任务的数量统计;
int hint:用于记录随机选择窃取任务,被窃取任务workQueue在数组中的下标值;
config:记录当前队列在数组中的下标和工作模式,高十六位记录工作模式,低十六位记录数组下标。如果下标为偶数的WorkQueue,则其mode是共享类型。如果有自己的owner 默认是 LIFO;
qlock: 锁标识(类似于AQS是state锁标识),使用此标识抢占锁。为1表示队列被锁定,为0表示未锁定,小于0代表当前队列注销或线程池关闭(terminate状态时为-1)
base:worker steal的偏移量(下一个pool操作的索引值(栈底/队列头部))。
top:owner执行任务的偏移量(下一个push操作的索引值(栈顶/队列尾部))。
array:存放任务的数组,初始化时不会分配空间,采用懒加载形式初始化空间;
pool:所属线程池的引用指向;
owner:当前队列所属线程的引用,如果为外部提交任务的共享队列则为null;
parker:如果 owner 挂起,则使用该变量做记录挂起owner的线程。
currentJoin: 当前正在join等待结果的任务。
currentSteal:当前执行的任务是steal过来的任务,该变量做记录。
static final class WorkQueue {
// 构造器
WorkQueue(ForkJoinPool pool, ForkJoinWorkerThread owner) {
this.pool = pool;
this.owner = owner;
// 开始的时候都指向栈顶
base = top = INITIAL_QUEUE_CAPACITY >>> 1;
}
// 添加方法:将一个任务添加进队列中
// 注意:索引不是通过数组下标计算的,而是通过计算内存偏移量定位
final void push(ForkJoinTask<?> task);
// 扩容方法:队列元素数量达到容量时,扩容两倍并移动元素到新数组
final ForkJoinTask<?>[] growArray();
// 获取方法:从栈顶(LIFO)弹出一个任务
final ForkJoinTask<?> pop();
// 获取方法:从栈底(FIFO)弹出一个任务
final ForkJoinTask<?> poll();
}
WorkQueue用数组存储所有待执行的任务,线程执行时会从该队列中获取任务,如果数组为空,那么则会尝试窃取其他线程的任务。即在ForkJoinPool中存在一个由WorkQueue构成的数组成员workQueues,而在每个WorkQueue中又存在一个ForkJoinTask构成的数组成员array。
总结
本篇粗略的看了一下ForkJoinPool的源码,还有更深入的没谈到,比如invoke的externalSubmit和externalPush,还有任务执行以及线程挂起,窃取的具体实现,任务的拆分与合并实现过程等,这部分以后有时间再聊聊。