forkjoin是java 7中的一个并行的线程执行框架,它最大的特点是能够把任务进行分割,这些分割之后的任务,是相互独立的,互不影响。当然分割这些任务的粒度是可以自己控制的,而且分割任务粒度的逻辑是必不可少的,比如说:你设定的粒度(阈值)为10 那么,你有一个大任务是100,那么你分得分割10次,而且分割时递归式的分割,每个任务是10,有10个任务。这10个任务是相互独立的。正是因为,任务是相互独立性的这个特定,使得在资源竞争比较激烈的并行框架中,使用forkjoin最为合适,它很好的解决了多线程资源竞争资源,出现的问题。这里,说的竞争资源,是cup资源。
forkjoin这个框架主要的线程池类是ForkJoinPool,它继承了AbstractExecutorService这并行框架抽象类。细心的读者可能会发现,在java 5中的并行框架ExecutorService的实现类ThreadPoolExecutor线程池类也继承了AbstractExecutorService。所以我们可以说forkjoin框架是java并行框架中的一个实现。我们也可以得出一个结论:AbstractExecutorService是并行线程池的抽象父类。说了那么多废话,那我们开始进入正题吧。
让我们看看ForkjoinPool这个类的主要构造方法里面做了些什么事情。
checkPermission();//坚持虚拟机是否有创建线程的权限
if (factory == null) //判断创建线程的线程工厂是否为null,否则抛出一个异常
throw new NullPointerException();
if (parallelism <= 0 || parallelism > MAX_ID)
throw new IllegalArgumentException();
this.parallelism = parallelism;//线程的并行度
this.factory = factory;//创建线程的线程工厂
this.ueh = handler;
this.locallyFifo = asyncMode;
long np = (long)(-parallelism); // offset ctl counts
this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
//创建一个任务队列,默认大小是8
this.submissionQueue = new ForkJoinTask[INITIAL_QUEUE_CAPACITY];
// initialize workers array with room for 2*parallelism if possible
int n = parallelism << 1;
//下面一段if代码很有意思,它会根据你传进来的并发度的大小,创建一个比并发度大小大2-3倍+1的线程池数组。
if (n >= MAX_ID)
n = MAX_ID;
else { // See Hackers Delight, sec 3.2, where n < (1 << 16)
n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8;
}
workers = new ForkJoinWorkerThread[n + 1];//创建线程池数组
//下面的两行代码就不用我多说了吧,相信大家都知道,至于有什么作用我稍后细说。
this.submissionLock = new ReentrantLock();
this.termination = submissionLock.newCondition();
//下面几行代码类似于日子功能,描述工作线程有多少个,一般只有一个。
StringBuilder sb = new StringBuilder("ForkJoinPool-");
sb.append(poolNumberGenerator.incrementAndGet());
sb.append("-worker-");
this.workerNamePrefix = sb.toString();
当外面创建一个ForkJoinPool对象之后,一般多会调用invoke,execute,submit等这些方法,这些方法他们都有类似的作用。作用是,首先会这些方法都会接受一个或多个任务(有无返回结果的任务),然后创建一个线程,并把这个线程加入大线程中,启动线程,调用任务的compute方法。接着我们来说说任务类吧。
RecursiveAction,RecursiveTask这两个类分别是无返回结果,和有结果返回的任务类,他们有一个主要的compute方法这个方法是用户自己实现业务逻辑的地方。这两个任务类都继承了ForkJoinTask这个主要的任务类,它有一个主要方法doExec会调用 exec这个方法,这个方法会调用用户的compute的方法,所以在forkjoin框架里面启动线程执行任务,它会调用doExec()这个方法。好了 逻辑讲完了我们来看看invoke,execute,submit的源码吧,我只抽取一个方法源码进行详细分析。
1》public void execute(ForkJoinTask task) {
if (task == null) //判断传进来的任务是否为空,为Null直接抛出异常
throw new NullPointerException();
forkOrSubmit(task);//
}
2》 private
ForkJoinWorkerThread w;
Thread t = Thread.currentThread();
if (shutdown)
throw new RejectedExecutionException();
if ((t instanceof ForkJoinWorkerThread) &&//判断当前线程是主线程还是任务线程,如果是任务线程那么久把任务放入到当前任务线程池中
(w = (ForkJoinWorkerThread)t).pool == this)
w.pushTask(task);
else
addSubmission(task); //如果是主线程那么将任务放到主线程池forkjoin中去。
}
3》 final void pushTask(ForkJoinTask t) {
ForkJoinTask[] q; int s, m;
if ((q = queue) != null) { // ignore if queue removed
long u = (((s = queueTop) & (m = q.length - 1)) << ASHIFT) + ABASE;
UNSAFE.putOrderedObject(q, u, t);//这段代码我不清楚是干啥了,请见谅
queueTop = s + 1; // or use putOrderedInt
if ((s -= queueBase) <= 2)//判断是否到了任务队列顶部,如果没有这调用signalWork这个方法会创建一个线程
pool.signalWork();
else if (s == m)
growQueue(); //如果判断到了任务队列的顶部,那么久会扩容
}
}
3》private void addSubmission(ForkJoinTask t) {
final ReentrantLock lock = this.submissionLock;//当前的forkjoinpool对象的锁
lock.lock();
try {
ForkJoinTask[] q; int s, m;
//下面一段代码回判断提交任务的个数是否超过了线程任务的大小。如果超过就扩容
if ((q = submissionQueue) != null) { // ignore if queue removed
long u = (((s = queueTop) & (m = q.length-1)) << ASHIFT)+ABASE;
UNSAFE.putOrderedObject(q, u, t);
queueTop = s + 1;
if (s - queueBase == m)
growSubmissionQueue();
}
} finally {
lock.unlock();
}
signalWork();
}
4》 final void signalWork() {
/*
* The while condition is true if: (there is are too few total
* workers OR there is at least one waiter) AND (there are too
* few active workers OR the pool is terminating). The value
* of e distinguishes the remaining cases: zero (no waiters)
* for create, negative if terminating (in which case do
* nothing), else release a waiter. The secondary checks for
* release (non-null array etc) can fail if the pool begins
* terminating after the test, and don't impose any added cost
* because JVMs must perform null and bounds checks anyway.
*/
long c; int e, u;
while ((((e = (int)(c = ctl)) | (u = (int)(c >>> 32))) &
(INT_SIGN|SHORT_SIGN)) == (INT_SIGN|SHORT_SIGN) && e >= 0) {
if (e > 0) { // release a waiting worker
int i; ForkJoinWorkerThread w; ForkJoinWorkerThread[] ws;
if ((ws = workers) == null ||
(i = ~e & SMASK) >= ws.length ||
(w = ws[i]) == null)
break;
long nc = (((long)(w.nextWait & E_MASK)) |
((long)(u + UAC_UNIT) << 32));
if (w.eventCount == e &&
UNSAFE.compareAndSwapLong(this, ctlOffset, c, nc)) {
w.eventCount = (e + EC_UNIT) & E_MASK;
if (w.parked)
UNSAFE.unpark(w);
break;
}
}
else if (UNSAFE.compareAndSwapLong
(this, ctlOffset, c,
(long)(((u + UTC_UNIT) & UTC_MASK) |
((u + UAC_UNIT) & UAC_MASK)) << 32)) {
addWorker();//关键是这段代码,其他的代码我不是很清楚请见谅。
break;
}
}
}
5》
private void addWorker() {
Throwable ex = null;
ForkJoinWorkerThread t = null;
try {
t = factory.newThread(this);//用线程工厂创建一个线程
} catch (Throwable e) {
ex = e;
}
if (t == null) { // null or exceptional factory return
long c; // adjust counts
do {} while (!UNSAFE.compareAndSwapLong
(this, ctlOffset, c = ctl,
(((c - AC_UNIT) & AC_MASK) |
((c - TC_UNIT) & TC_MASK) |
(c & ~(AC_MASK|TC_MASK)))));
// Propagate exception if originating from an external caller
if (!tryTerminate(false) && ex != null &&
!(Thread.currentThread() instanceof ForkJoinWorkerThread))
UNSAFE.throwException(ex);
}
else
t.start();//启动线程
}
6》
//这个方法是用线程工厂创建的也是就上面 t = factory.newThread(this);这一段代码
public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
return new ForkJoinWorkerThread(pool);
}
7》
//当第5阶段调用start方法之后,就会执行这个run方法
public void run() {
Throwable exception = null;
try {
onStart();
pool.work(this);//这是将当前线程加入到线程池中去,
} catch (Throwable ex) {
exception = ex;
} finally {
onTermination(exception);
}
}
8》
final void work(ForkJoinWorkerThread w) {
boolean swept = false; // true on empty scans
long c;
while (!w.terminate && (int)(c = ctl) >= 0) {
int a; // active count
if (!swept && (a = (int)(c >> AC_SHIFT)) <= 0)
swept = scan(w, a);//这是主要方法
else if (tryAwaitWork(w, c))
swept = false;
}
}
9》
private boolean scan(ForkJoinWorkerThread w, int a) {
int g = scanGuard; // mask 0 avoids useless scans if only one active
int m = (parallelism == 1 - a && blockedCount == 0) ? 0 : g & SMASK;
ForkJoinWorkerThread[] ws = workers;
if (ws == null || ws.length <= m) // staleness check
return false;
for (int r = w.seed, k = r, j = -(m + m); j <= m + m; ++j) {
ForkJoinTask t; ForkJoinTask[] q; int b, i;
ForkJoinWorkerThread v = ws[k & m];
if (v != null && (b = v.queueBase) != v.queueTop &&
(q = v.queue) != null && (i = (q.length - 1) & b) >= 0) {
long u = (i << ASHIFT) + ABASE;
if ((t = q[i]) != null && v.queueBase == b &&
UNSAFE.compareAndSwapObject(q, u, t, null)) {
int d = (v.queueBase = b + 1) - v.queueTop;
v.stealHint = w.poolIndex;
if (d != 0)
signalWork(); // propagate if nonempty 这个方法我们很熟悉了吧,就是创建一个线程执行任务
w.execTask(t);//这个方法是重点
}
r ^= r << 13; r ^= r >>> 17; w.seed = r ^ (r << 5);
return false; // store next seed
}
else if (j < 0) { // xorshift
r ^= r << 13; r ^= r >>> 17; k = r ^= r << 5;
}
else
++k;
}
if (scanGuard != g) // staleness check
return false;
else { // try to take submission
ForkJoinTask t; ForkJoinTask[] q; int b, i;
if ((b = queueBase) != queueTop &&
(q = submissionQueue) != null &&
(i = (q.length - 1) & b) >= 0) {
long u = (i << ASHIFT) + ABASE;
if ((t = q[i]) != null && queueBase == b &&
UNSAFE.compareAndSwapObject(q, u, t, null)) {
queueBase = b + 1;
w.execTask(t);
}
return false;
}
return true; // all queues empty
}
}
10》
final void execTask(ForkJoinTask t) {
currentSteal = t;
for (;;) {
if (t != null)
t.doExec();//这个方法我们很熟悉吧,它会调用exec()()这个方法
if (queueTop == queueBase)
break;
t = locallyFifo ? locallyDeqTask() : popTask();
}
++stealCount;
currentSteal = null;
}
11》
final void doExec() {
if (status >= 0) {
boolean completed;
try {
completed = exec();//这个方法会返回任务状态
} catch (Throwable rex) {
setExceptionalCompletion(rex);
return;
}
if (completed)
setCompletion(NORMAL); // must be outside try block
}
}
12》
protected final boolean exec() {
result = compute();//这个方法 终于是我们最主要的方法,用户自定义的方法,实现。
return true;
}
以上12个阶段,真是太绕了,其实我们主要关心compute方法什么时候被调用。我们理清下思路,forkjoinPool线程池对象,它自己有个任务队列默认是8。这个线程池有一个任务队列,这个队列放原始任务的,也就是说大任务 比如说:一个任务为40,任务粒度为10 那么一共可以分为4个任务,也就是说这个任务队列会放,大任务为40。这个大任务,是由主线程(main)线程去创建一个ForkJoinWorkerThread类型的线程,由这个线程去执行第一个任务,也就是40这个任务,发现40这个任务的粒度不满足,继续分割,分割之后,你需要调用fork和join方法,让ForkJoinWorkerThread,去创建一个子线程,这个过程是递归的过程。总而言之,大概的逻辑是这样的。这是一部分,我之后还会对其他的方法做深入研究。