簡介
ForkJoinPool管理着ForkJoinWorkerThread線程,ForkJoinWorkerThread線程內部有一個雙端隊列,這個雙端隊列主要由一個數組queue、數組下標queueBase、數組上標queueTop三個值保證。
ForkJoinTask<?>[] queue:數組的大小必須是2的n次方,方便將取模轉換爲移位運算;
int queueTop:標識下一個被push或者pop的位置,這個值只會被當前線程修改,因些沒有加volatile修飾;
volatile int queueBase:下一個可以被其他線程steal的位置,由於其他線程會修改這個值,所以用volatile修飾保證可見性。
初始化
在線程的run方法啓動時,會調用線程的onStart()方法,在這個方法中對queue進行了初始化,長度爲1 << 13,這個方法並沒有對queueTop,queueBase進行賦值,採用默認值0。
擴容
當向線程中添加任務時,有可能會導致數組滿的情況,如下代碼所示:
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)
pool.signalWork();
else if (s == m)
growQueue();
}
}
其中s代表queueTop的值,m爲數組長度-1,當s == m時,也就是queue數組中都放滿任務了,這時需要對數組進行擴容。
private void growQueue() {
ForkJoinTask<?>[] oldQ = queue;
int size = oldQ != null ? oldQ.length << 1 : INITIAL_QUEUE_CAPACITY;
if (size > MAXIMUM_QUEUE_CAPACITY)
throw new RejectedExecutionException("Queue capacity exceeded");
if (size < INITIAL_QUEUE_CAPACITY)
size = INITIAL_QUEUE_CAPACITY;
ForkJoinTask<?>[] q = queue = new ForkJoinTask<?>[size];
int mask = size - 1;
int top = queueTop;
int oldMask;
if (oldQ != null && (oldMask = oldQ.length - 1) >= 0) {
for (int b = queueBase; b != top; ++b) {
long u = ((b & oldMask) << ASHIFT) + ABASE;
Object x = UNSAFE.getObjectVolatile(oldQ, u);
if (x != null && UNSAFE.compareAndSwapObject(oldQ, u, x, null))
UNSAFE.putObjectVolatile
(q, ((b & mask) << ASHIFT) + ABASE, x);
}
}
}
從以上擴容代碼可以看出,最大容量不能超過MAXIMUM_QUEUE_CAPACITY(1 << 24),最小不能小於初始值。每次擴容爲先前大小的2倍,將原始數組複製到新數組中,同時將舊數組置null。擴容的過程中,queueBase和queueTop並不需要變化。
入隊列
向線程隊列中添加一個任務,或者向線程池添加一個任務時,如果這個任務是一個ForkJoinTask實例,就會做入隊列的操作。前面已有這段代碼,這裏簡要分析一下
long u = (((s = queueTop) & (m = q.length - 1)) << ASHIFT) + ABASE;
UNSAFE.putOrderedObject(q, u, t);
queueTop = s + 1;
第一行,找到queueTop在數組中的位置
第二行,用新任務填充queueTop所在位置
第三行,queueTop加1.
出隊列
本地線程需要執行一個任務
final void execTask(ForkJoinTask<?> t) {
currentSteal = t;
for (;;) {
if (t != null)
t.doExec();
if (queueTop == queueBase)
break;
t = locallyFifo ? locallyDeqTask() : popTask();
}
++stealCount;
currentSteal = null;
}
注意locallyFifo 這個屬性,是否對自己的隊列採用FIFO策略,默認爲false,即默認從queueTop一端取任務。如果這個值爲false,則從queueBase一端取數據。這個值可以通過ForkJoinPool類的asyncMode屬性加以修改。final ForkJoinTask<?> locallyDeqTask() {
ForkJoinTask<?> t; int m, b, i;
ForkJoinTask<?>[] q = queue;
if (q != null && (m = q.length - 1) >= 0) {
while (queueTop != (b = queueBase)) {
if ((t = q[i = m & b]) != null &&
queueBase == b &&
UNSAFE.compareAndSwapObject(q, (i << ASHIFT) + ABASE,
t, null)) {
queueBase = b + 1;
return t;
}
}
}
return null;
}
private ForkJoinTask<?> popTask() {
int m;
ForkJoinTask<?>[] q = queue;
if (q != null && (m = q.length - 1) >= 0) {
for (int s; (s = queueTop) != queueBase;) {
int i = m & --s;
long u = (i << ASHIFT) + ABASE; // raw offset
ForkJoinTask<?> t = q[i];
if (t == null) // lost to stealer
break;
if (UNSAFE.compareAndSwapObject(q, u, t, null)) {
queueTop = s; // or putOrderedInt
return t;
}
}
}
return null;
}
關鍵兩句話:queueBase = b + 1:FIFO策略每次從queueBase取任務,每取一個,queueBase增加1;
--s,queueTop = s:LIFO策略每次從queueTop取任務,每取一個,queueTop減1。
其他線程需要偷一個任務執行
以下是work-stealing的核心代碼
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;
}
}
1、瞄到第i個位置這個任務,i = (q.length-1) & b,i其實就是queueBase在數組中所在的位置;2、將這個位置上的任務設置爲null,並增加queueBase的值,設置stealHint表示你的東西被我偷了;
3、保存先前的currentSteal值,設置currentSteal爲這個偷來的task,然後執行這個task,執行完後,恢復currentSteal的值。