Fork
Fork就是一個不斷分枝的過程,在當前任務的基礎上長出n多個子任務。
當一個ForkJoinTask任務調用fork()方法時,當前線程會把這個任務放入到queue數組的queueTop位置,然後執行以下兩句代碼:
if ((s -= queueBase) <= 2)
pool.signalWork();
else if (s == m)
growQueue();
其中s=queueTop,m爲數組length減1。else if部分,表示數組所有元素都滿了,需要擴容,不難理解。if部分表示當數組元素比較少時(1或者2),就調用signalWork()方法。signalWork()方法做了兩件事:1、喚配當前線程;2、當沒有活動線程時或者線程數較少時,添加新的線程。
Join
Join是一個不斷等待,獲取任務執行結果的過程。
private int doJoin() {
Thread t; ForkJoinWorkerThread w; int s; boolean completed;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) {
if ((s = status) < 0)
return s;
if ((w = (ForkJoinWorkerThread)t).unpushTask(this)) {
try {
completed = exec();
} catch (Throwable rex) {
return setExceptionalCompletion(rex);
}
if (completed)
return setCompletion(NORMAL);
}
return w.joinTask(this);
}
else
return externalAwaitDone();
}
(1)第4行,(s=status)<0表示這個任務被執行完,直接返回執行結果狀態,上層捕獲到狀態後,決定是要獲取結果還是進行錯誤處理;
(2)第6行,從queue中取出這個任務來執行,如果執行完了,就設置狀態爲NORMAL;
(3)前面unpushTask()方法在隊列中沒有這個任務時會返回false,15行調用joinTask等待這個任務完成。
(4)由於ForkJoinPool中有一個數組叫submissionQueue,通過submit方法調用而且非ForkJoinTask這種任務會被放到這個隊列中。這種任務有可能被非ForkJoinWorkerThread線程執行,第18行表示如果是這種任務,等待它執行完成。
下面來看joinTask方法
final int joinTask(ForkJoinTask<?> joinMe) {
ForkJoinTask<?> prevJoin = currentJoin;
currentJoin = joinMe;
for (int s, retries = MAX_HELP;;) {
if ((s = joinMe.status) < 0) {
currentJoin = prevJoin;
return s;
}
if (retries > 0) {
if (queueTop != queueBase) {
if (!localHelpJoinTask(joinMe))
retries = 0; // cannot help
}
else if (retries == MAX_HELP >>> 1) {
--retries; // check uncommon case
if (tryDeqAndExec(joinMe) >= 0)
Thread.yield(); // for politeness
}
else
retries = helpJoinTask(joinMe) ? MAX_HELP : retries - 1;
}
else {
retries = MAX_HELP; // restart if not done
pool.tryAwaitJoin(joinMe);
}
}
}
(1)這裏有個常量MAX_HELP=16,表示幫助join的次數。第11行,queueTop!=queueBase表示本地隊列中有任務,如果這個任務剛好在隊首,則嘗試自己執行;否則返回false。這時retries被設置爲0,表示不能幫助,因爲自已隊列不爲空,自己並不空閒。在下一次循環就會進入第24行,等待這個任務執行完成。
(2)第20行helpJoinTask()方法返回false時,retries-1,連續8次都沒有幫到忙,就會進入第14行,調用yield讓權等待。沒辦法人口太差,想做點好事都不行,只有停下來休息一下。
(3)當執行到第20行,表示自己隊列爲空,可以去幫助這個任務了,下面來看是怎麼幫助的?
outer:for (ForkJoinWorkerThread thread = this;;) {
// Try to find v, the stealer of task, by first using hint
ForkJoinWorkerThread v = ws[thread.stealHint & m];
if (v == null || v.currentSteal != task) {
for (int j = 0; ;) { // search array
if ((v = ws[j]) != null && v.currentSteal == task) {
thread.stealHint = j;
break; // save hint for next time
}
if (++j > m)
break outer; // can't find stealer
}
}
// Try to help v, using specialized form of deqTask
for (;;) {
ForkJoinTask<?>[] q; int b, i;
if (joinMe.status < 0)
break outer;
if ((b = v.queueBase) == v.queueTop ||
(q = v.queue) == null ||
(i = (q.length-1) & b) < 0)
break; // empty
long u = (i << ASHIFT) + ABASE;
ForkJoinTask<?> t = q[i];
if (task.status < 0)
break outer; // stale
if (t != null && v.queueBase == b &&
UNSAFE.compareAndSwapObject(q, u, t, null)) {
v.queueBase = b + 1;
v.stealHint = poolIndex;
ForkJoinTask<?> ps = currentSteal;
currentSteal = t;
t.doExec();
currentSteal = ps;
helped = true;
}
}
// Try to descend to find v's stealer
ForkJoinTask<?> next = v.currentJoin;
if (--levels > 0 && task.status >= 0 &&
next != null && next != task) {
task = next;
thread = v;
}
}
(1)通過查看stealHint這個字段的註釋可以知道,它表示最近一次誰來偷過我的queue中的任務。因此通過stealHint並不能找到當前任務被誰偷了?所以第4行v.currentSteal != task完全可能。這時還有一個辦法找到這個任務被誰偷了,看看currentSteal這個字段的註釋表示最近偷的哪個任務。這裏掃描所有偷來的任務與當前任務比較,如果相等,就是這個線程偷的。如果這兩種方法都不能找到小偷,只能等待了。
(2)當找到了小偷後,以其人之身還之其人之道,從小偷那裏偷任務過來,相當於你和小偷共同執行你的任務,會加速你的任務完成。
(3)小偷也是爺,如果小偷也在等待一個任務完成,權利反轉(小偷等待的這個任務做爲當前任務,小偷扮演當事人角色把前面的流程走一遍),這是一個遞歸的過程。