Fork/Join
框架中两个核心类ForkJoinTask
与ForkJoinPool
,声明ForkJoinTask
后,将其加入ForkJoinPool
中,并返回一个Future
对象。
ForkJoinPool
:ForkJoinTask
需要通过ForkJoinPool
来执行,任务分割的子任务会添加到当前工作维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其它工作线程的队列尾部获取一个任务。ForkJoinTask
:我们需要使用ForkJoin
框架,首先要创建一个ForkJoin
任务。它提供在任务中执行Fork()
和Join()
操作的机制,通常情况下不需要直接继承ForkJoinTask
类,而只需要继承它的子类,Fork/Join
框架提供以下两个子类。RecursiveAction
:用于没有返回值的任务。RecursizeTask
:用于有返回值的任务。
Exception
ForkJoinTask
在执行的时候可能会抛出异常,但是我们没有办法直接在主线程里捕获异常,所以ForkJoinTask
提供了isCompletedAbnormally()
方法来检查任务是否已经抛出异常或已经被取消了,并且可以通过ForkJoinTask
的getException
方法捕获异常。
public abstract class ForkJoinTask implements Future, Serializable {
/** ForkJoinTask运行状态 */
volatile int status; // 直接被ForkJoin池和工作线程访问
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
/**
* @Ruturn 任务是否扔出异常或被取消
*/
public final boolean isCompletedAbnormally() {
return status < NORMAL;
}
/**
* 如果计算扔出异常,则返回异常
* 如果任务被取消了则返回CancellationException。如果任务没有完成或者没有抛出异常则返回null
*/
public final Throwable getException() {
int s = status & DONE_MASK;
return ((s >= NORMAL) ? null :
(s == CANCELLED) ? new CancellationException() :
getThrowableException());
}
}
ForkJoinPool
源码
public class ForkJoinPool extends AbstractExecutorService {
/**
* ForkJoinPool,它同ThreadPoolExecutor一样,也实现了Executor和ExecutorService接口。它使用了
* 一个无限队列来保存需要执行的任务,而线程的数量则是通过构造函数传入,如果没有向构造函数中传入希
* 望的线程数量,那么当前计算机可用的CPU数量会被设置为线程数量作为默认值。
*/
public ForkJoinPool() {
this(Math.min(MAX_CAP,Runtime.getRuntime().availableProcessors()),
defaultForkJoinWorkerThreadFactory, null, false);
}
public ForkJoinPool(int parallelism) {
this(parallelism, defaultForkJoinWorkerThreadFactory, null, false);
}
//有多个构造器,这里省略
volatile WorkQueue[] workQueues; // main registry
static final class WorkQueue {
final ForkJoinWorkerThread owner; // 工作线程
ForkJoinTask>[] array; // 任务
//传入的是ForkJoinPool与指定的一个工作线程
WorkQueue(ForkJoinPool pool, ForkJoinWorkerThread owner) {
this.pool = pool;
this.owner = owner;
// Place indices in the center of array (that is not yet allocated)
base = top = INITIAL_QUEUE_CAPACITY >>> 1;
}
}
}
FrokJoinPool
work stealing算法
ForkJoinPool
维护了一组WorkQueue
,也就是工作队列,工作队列中又维护了一个工作线程ForkJoinWorkerThread
与一组工作任务ForkJoinTask
WorkQueue
是一个双端队列Deque(Double Ended Queue)
,Deque
是一种具有队列和栈性质的数据结构,双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。每个工作线程在运行中产生新的任务(通常因为调用了
fork()
)时,会放在工作队列的对尾,并且工作线程在处理自己的工作队列时,使用的是LIFO
,也就是说每次从队列尾部取任务来执行。每个工作线程在处理自己的工作队列同时,会尝试窃取一个任务(或是来自于刚刚提交到 pool 的任务,或是来自于其它工作线程的工作队列),窃取的任务位于其他线程的工作队列的队首,也就是说工作线程在窃取其他工作线程的任务时,使用的是 FIFO 方式。
在遇到
Join()
时,如果需要Join
的任务尚未完成,则会优先处理其它任务,并等待其完成。在没有自己的任务时,也没有任何可以窃取时,则进入休眠。
public class ForkJoinPool extends AbstractExecutorService {
public ForkJoinTask submit(ForkJoinTask task) {}
public ForkJoinTask submit(Callable task) {}
public ForkJoinTask submit(Runnable task, T result) {}
public ForkJoinTask> submit(Runnable task) {}
}
ForkJoinPool
自身也拥有工作队列,这些工作队列的作用是用来接收由外部线程(非ForkJoinThread
线程)提交过来的任务,而这些工作队列被称为submitting queue
。
ForkJoinTask
任务的操作,重要的是fork()
和join()
。
public abstract class ForkJoinTask implements Future, Serializable {
/**
* 在当前任务正在运行的池中异步执行此任务(如果适用)
* 或使用ForkJoinPool.commonPool()(如果不是ForkJoinWorkerThread实例)进行异步执行
*/
public final ForkJoinTask fork() {
Thread t;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
ForkJoinPool.common.externalPush(this);
return this;
}
public final V join() {
int s;
if ((s = doJoin() & DONE_MASK) != NORMAL)
reportException(s);
return getRawResult();
}
private int doJoin() {
int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
return (s = status) < 0 ? s :
((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
(w = (wt = (ForkJoinWorkerThread)t).workQueue).
tryUnpush(this) && (s = doExec()) < 0 ? s :
wt.pool.awaitJoin(w, this, 0L) :
externalAwaitDone();
}
}
fork()
做的工作只有一件事,就是把当前任务推入当前线程的工作队列里。
join()
的工作就比较复杂,也是join()
可以使的线程免于被阻塞的原因。
- 检查调用
join()
的线程是否是ForkJoinThread
线程。如果不是(例如main线程),则阻塞当前线程,等待任务完成。如果是,则不阻塞。 - 检查任务的完成状态,如果已经完成,则直接返回结果。
- 如果任务尚未完成,但是处理自己的工作队列,则完成它。
- 如果任务已经被其它线程偷走,则这个小偷工作队列的任务以先进先出的方式执行,帮助小偷线程尽快完成
join
- 如果偷走任务的小偷也已经把自己的任务全部做完,正在等待需要
join
的任务时,则找到小偷的小偷(递归执行),帮助它完成它的任务。
ForkJoinPool.submit
方法
public static void main(String[] args) throws ExecutionException, InterruptedException {
//生成一个池
ForkJoinPool forkJoinPool=new ForkJoinPool();
ForkJoinTask task=new ForkJoinExample(1, 100000);
ForkJoinTask submit = forkJoinPool.submit(task);
Integer sum = submit.get();
System.out.println("最后的结果是:"+sum);
}
每个工作线程自己拥有的工作队列以外,ForkJoinPool
自身也拥有工作队列,这些工作队列的作用是用来接收有外部线程(非ForkJoinPool
)提交过来的任务,而这些工作队列被称为submitting queue
。
submit()
和fork()
没有本质区别,只是提交对象变成了submitting queue
(还有一些初始化,同步操作)。submitting queue
和其它work queue
一样,是工作线程窃取的对象,因此当其中的任务被一个工作线程成功窃取时,也就意味着提交的任务真正开始进入执行阶段。
关注微信公众号:【入门小站】,解锁更多知识点。