1 ForkJoin框架
1.1 ForkJoin框架
ForkJoinPool一種ExecutorService的實現,運行ForkJoinTask任務。ForkJoinPool區別於其它ExecutorService,主要是因爲它採用了一種工作竊取(work-stealing)的機制。所有被ForkJoinPool管理的線程嘗試竊取提交到池子裏的任務來執行,執行中又可產生子任務提交到池子中。
ForkJoinPool維護了一個WorkQueue的數組(數組長度是2的整數次方,自動增長)。每個workQueue都有任務隊列(ForkJoinTask的數組),並且用base、top指向任務隊列隊尾和隊頭。work-stealing機制就是工作線程挨個掃描任務隊列,如果隊列不爲空則取隊尾的任務並執行。示意圖如下:
1.2 demo小程序
創建一個包含2個worker線程的pool,main線程提交2個任務(task-1、task-2),觸發worker線程工作,task-1任務fork出4個子任務。main線程負責同步兩個worker線程的工作進度。demo小程序演示了worker-2竊取worker-1的子任務:
package com.focuse.jdkdemo.concurrent;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
/**
* @date :Created in 2020/2/16 上午11:10
* @description:
* @modified By:
*/
public class ForkJoinDemo {
private static Thread worker1 = null;
private static Thread worker2 = null;
private static AtomicBoolean worker1Park = new AtomicBoolean();
private static AtomicBoolean worker2Park = new AtomicBoolean();
static {
worker1Park.set(false);
worker2Park.set(false);
}
public abstract static class ForkJoinTaskDemo extends ForkJoinTask {
@Override
public Object getRawResult() {
return null;
}
@Override
protected void setRawResult(Object value) {
}
}
public static void setParkFlag() {
if (Thread.currentThread().getName().equals("worker-1")) {
//自旋設置, 保證與main線程同步
while (!worker1Park.compareAndSet(false, true)) {
}
} else if (Thread.currentThread().getName().equals("worker-2")) {
//自旋設置, 保證與main線程同步
while (!worker2Park.compareAndSet(false, true)) {
}
}
}
private static Runnable task1 = new Runnable() {
@Override
public void run() {
worker1 = Thread.currentThread();
worker1.setName("worker-1");
System.out.println(Thread.currentThread().getName() + " execute task1");
//暫停並且設置暫停flag以便main結束自旋
setParkFlag();
LockSupport.park();
ForkJoinTask task11 = new ForkJoinTaskDemo() {
@Override
protected boolean exec() {
System.out.println(Thread.currentThread().getName() + " execute task1-1");
//暫停並且設置暫停flag以便main結束自旋
setParkFlag();
LockSupport.park();
return true;
}
};
task11.fork();
ForkJoinTask task12 = new ForkJoinTaskDemo() {
@Override
protected boolean exec() {
System.out.println(Thread.currentThread().getName() + " execute task1-2");
//暫停並且設置暫停flag以便main結束自旋
setParkFlag();
LockSupport.park();
return true;
}
};
task12.fork();
ForkJoinTask task13 = new ForkJoinTaskDemo() {
@Override
protected boolean exec() {
System.out.println(Thread.currentThread().getName() + " execute task1-3");
//暫停並且設置暫停flag以便main結束自旋
setParkFlag();
LockSupport.park();
return true;
}
};
task13.fork();
ForkJoinTask task14 = new ForkJoinTaskDemo() {
@Override
protected boolean exec() {
System.out.println(Thread.currentThread().getName() + " execute task1-4");
//暫停並且設置暫停flag以便main結束自旋
setParkFlag();
LockSupport.park();
return true;
}
};
task14.fork();
//提交4個子任務後暫停
setParkFlag();
LockSupport.park();
}
};
private static Runnable task2 = new Runnable() {
@Override
public void run() {
worker2 = Thread.currentThread();
worker2.setName("worker-2");
System.out.println(Thread.currentThread().getName() + " execute task2");
//暫停並且設置暫停flag以便main結束自旋
setParkFlag();
LockSupport.park();
}
};
public static void main(String[] args) {
//只用兩個線程,方便測試
ForkJoinPool pool = new ForkJoinPool(2);
//step-1、step-2 提交2各任務,期望worker-1 worker-2各執行一個任務
System.out.println("step-1 step-2: 提交2個任務,觸發2個worker線程");
pool.submit(task1);
pool.submit(task2);
while (!worker1Park.get()) {
}
//step-3 喚醒worker-1 產生4個任務 然後worker-1繼續暫停
System.out.println("\n*******************************************");
System.out.println("step-3 喚醒worker-1 產生4個子任務");
LockSupport.unpark(worker1);
//自旋設置, 保證與worker-1線程同步
while (!worker1Park.compareAndSet(true, false)) {
}
while (!worker1Park.get() || !worker2Park.get()) {
}
//step-4 喚醒worker-1 worker-2
System.out.println("\n*******************************************");
System.out.println("step-4 喚醒worker-1、worker-2:worker-1彈出task1-4執行,worker-2竊取task1-1執行");
LockSupport.unpark(worker1);
LockSupport.unpark(worker2);
//自旋設置, 保證與worker-1線程同步
while (!worker1Park.compareAndSet(true, false)) {
}
//自旋設置, 保證與worker-2線程同步
while (!worker2Park.compareAndSet(true, false)) {
}
while (!worker1Park.get() || !worker2Park.get()) {
}
//step-5 喚醒worker-1 worker-2
System.out.println("\n*******************************************");
System.out.println("step-5 喚醒worker-1、worker-2:worker-1彈出task1-3執行,worker-2竊取task1-2執行");
LockSupport.unpark(worker1);
LockSupport.unpark(worker2);
//自旋設置, 保證與worker-1線程同步
while (!worker1Park.compareAndSet(true, false)) {
}
//自旋設置, 保證與worker-2線程同步
while (!worker2Park.compareAndSet(true, false)) {
}
while (!worker1Park.get() || !worker2Park.get()) {
}
//喚醒worker-1 worker-2 結束
LockSupport.unpark(worker1);
LockSupport.unpark(worker2);
//自旋設置, 保證與worker-1線程同步
while (!worker1Park.compareAndSet(true, false)) {
}
//自旋設置, 保證與worker-2線程同步
while (!worker2Park.compareAndSet(true, false)) {
}
}
}
運行結果如下:
2 源碼解讀
這一part圍繞線程池(ForkJoinPool)結構已經線程池的關鍵動作講一下過程,線程池的關鍵動作無非是提交任務、運行任務、獲取任務結果。不過,對於ForkJoinPool有還有任務fork動作。
ForkJoinPool裏面大量用到bit運算。做一下簡短說明:計算機的運算以補碼計算。補碼怎麼來?正數的補碼就是原碼,最高位是符號位爲0;負數的補碼是其正數的補碼按位取反(包括符號位),最後加1。
2.1 ForkJoinPool 和 WorkQueue
ForkJoinPool幾個主要的成員變量說明如下:
- config 是創建ForkJoinPool的配置,int類型32bits,高16位表示pool的mode(FIFO或LIFO),低16位表示parallelism(並行度,默認大小可用處理器數java.lang.Runtime#availableProcessors);
- ctl 是ForkJoinPool的主要控制字段,long類型64bits,ctl不同的bit位表示不同的含義;
- 高16位(63~48)表示活躍的線程,值爲活躍線程數減去parallelism(補碼錶示),初始值是0-parallelism,工作線程激活則加1,去激活則減1。當累積加了parallelism時第63bit位翻轉爲0,則不允許再激活工作線程。
- 第47~32位表示當前所有工作線程(包括未激活的),值爲所有工作線程數-parallelism(補碼錶示),創建線程則加1,終止線程則減1。當累積加了parallelism時第47位翻轉位0,則不允許再創建線程;
- 第31~16位表示非激活線程鏈中top線程的版本計數和線程狀態,與第15~0位合起來看;
- 第15~0位表示非激活線程鏈中top線程的本地WorkQueue在ForkJoinPool.workQueues數組中下標索引,第31~0位合起來的值實際是非激活線程鏈中top線程的本地WorkQueue.scanState
- workQueues 是ForkJoinPool維護一個WorkQueue數組,奇數下標的WorkQueue關聯一個worker線程,偶數下標的WorkQueue用來接收外部提交的任務(非worker線程提交的任務);
- factory 創建worker線程的工廠;
源碼如下(筆者添加了註釋):
public class ForkJoinPool extends AbstractExecutorService {
......
//筆者注:runState的bit位:SHUTDOWN是負數(int的最高bit位爲1),其餘的是2的整數次方
private static final int RSLOCK = 1;
private static final int RSIGNAL = 1 << 1;
private static final int STARTED = 1 << 2;
private static final int STOP = 1 << 29;
private static final int TERMINATED = 1 << 30;
private static final int SHUTDOWN = 1 << 31;
// Instance fields
//筆者注:ctl是ForkJoinPool的控制字段。long是64位bit,
//最高16位(63~48)表示活躍的線程,值爲活躍的線程-parallelism(補碼錶示),
//第47~32位表示當前所有工作線程(包括未激活的),值爲所有工作線程數-parallelism(補碼錶示),
//第31~16位表示waiters線程鏈中top線程的版本計數和線程狀態
//第15~0位表示waiters線程鏈中top線程的本地WorkQueue在ForkJoinPool.workQueues的下標索引
volatile long ctl; // main pool control
volatile int runState; // lockable status
//筆者注:config高16位是mode(FIFO或者LIFO),低16位是parallelism
final int config; // parallelism, mode
//筆者注:防止index衝突的隨機數種子
int indexSeed; // to generate worker index
//筆者注:WorkQueue數組,奇數WorkQueue有worker線程,偶數WorkQueue接收外部提交任務
volatile WorkQueue[] workQueues; // main registry
//工作線程的創建工廠
final ForkJoinWorkerThreadFactory factory;
final UncaughtExceptionHandler ueh; // per-worker UEH
final String workerNamePrefix; // to create worker name string
//工作竊取的計數
volatile AtomicLong stealCounter; // also used as sync monitor
......
}
WorkQueue的幾個重要成員變量說明如下:
- scanState int類型32bits,各bit位含義如下:
- 第31位表示線程狀態(1非激活),第30~16位表示版本計數;
- 第0位表示worker線程是否在運行任務(1-scanning,0-busy),這裏有個小技巧,在創建worker線程的WorkQueue時scanState的第15~0位初始化爲ForkJoinPool.workQueues的下標(worker線程的WorkQueue的下標是奇數),當worker線程運行任務時第0位設置0(busy),任務運行結束第0位又設置1(恢復爲奇數),所以scanState的第15~0又可以表示在ForkJoinPool.workQueues數組的下標索引;
- stackPred 當worker線程從激活變爲非激活時設置值,且值爲ForkJoinPool的ctl的低32位(實際是前一個非激活線程),這樣就形成了一個非激活線程鏈;
- config 高16位是mode(FIFO或者LIFO),低16位是ForkJoinPool.workQueues的下標;
- base 任務隊列的隊尾,工作竊取就是竊取base指向的任務;
- top 任務隊列的隊頭(指向空),下一個push的位置;
- array 任務隊列;
- pool 所屬的ForkJoinPool實例;
- owner 所屬的worker線程,如果在ForkJoinPool.workQueues數組中下標是奇數,則不爲空。
源碼如下(筆者添加了註釋):
static final class WorkQueue {
......
// Instance fields
//筆者注:最高位爲1表示非激活,第30~16位版本計數,第0表示是否在運行任務(1-scanning,0-busy)
//
volatile int scanState; // versioned, <0: inactive; odd:scanning
//筆者注:實際是前一個非激活線程,這樣就形成了一個waiters線程鏈
int stackPred; // pool stack (ctl) predecessor
int nsteals; // number of steals
int hint; // randomization and stealer index hint
//高16位是mode(FIFO或LIFO),低16位是ForkJoinPool.workQueues的下標
int config; // pool index and mode
volatile int qlock; // 1: locked, < 0: terminate; else 0
//筆者注:任務隊列的隊尾,工作竊取就是竊取base指向的任務
volatile int base; // index of next slot for poll
//筆者注:任務隊列的隊頭(指向空),下一個push的位置
int top; // index of next slot for push
//筆者注:任務隊列
ForkJoinTask<?>[] array; // the elements (initially unallocated)
//筆者注:所屬的ForkJoinPool實例
final ForkJoinPool pool; // the containing pool (may be null)
//筆者注:所屬的worker線程,如果在ForkJoinPool.workQueues數組中的下標是奇數則不爲空
final ForkJoinWorkerThread owner; // owning thread or null if shared
volatile Thread parker; // == owner during call to park; else null
volatile ForkJoinTask<?> currentJoin; // task being joined in awaitJoin
volatile ForkJoinTask<?> currentSteal; // mainly used by helpStealer
......
}
2.2 提交任務
ForkJoinPool作爲ExecutorService的一個實現類,有submit方法提交任務,直接貼源碼出來如下:
public class ForkJoinPool extends AbstractExecutorService {
......
//提交任務
public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) {
if (task == null)
throw new NullPointerException();
externalPush(task);
return task;
}
......
}
submit方法判斷了一下任務是否爲null,然後直接調用externalPush,源碼如下:
public class ForkJoinPool extends AbstractExecutorService {
......
/**
**/
final void externalPush(ForkJoinTask<?> task) {
WorkQueue[] ws; WorkQueue q; int m;
int r = ThreadLocalRandom.getProbe();
int rs = runState;
//筆者注:(1)計算一個偶數下標,如果該下標下WorkQueue不爲空則嘗試添加到該WorkQueue
if ((ws = workQueues) != null && (m = (ws.length - 1)) >= 0 &&
(q = ws[m & r & SQMASK]) != null && r != 0 && rs > 0 &&
U.compareAndSwapInt(q, QLOCK, 0, 1)) {// 筆者注:(2)加鎖鎖住改WorkQueue
ForkJoinTask<?>[] a; int am, n, s;
//筆者注:WorkQueue的任務隊列不爲null且未滿 a.length - 1 > q.top - q.base
if ((a = q.array) != null &&
(am = a.length - 1) > (n = (s = q.top) - q.base)) {
int j = ((am & s) << ASHIFT) + ABASE;
U.putOrderedObject(a, j, task);
U.putOrderedInt(q, QTOP, s + 1);
//筆者注:釋放鎖
U.putIntVolatile(q, QLOCK, 0);
//筆者注:喚醒工作線程
if (n <= 1)
signalWork(ws, q);
return;
}
//筆者注:釋放鎖
U.compareAndSwapInt(q, QLOCK, 1, 0);
}
externalSubmit(task);
}
......
}
submit的任務是提交到偶數下標的workQueue中。函數externalPush先計算一個下標位置"m & r & SQMASK",這個位置是偶數下標,如何保證是偶數?"m & r & SQMASK"又是什麼含義?m是pool.workQueues的數組size - 1(即2的整數次方-1),跟m做&運算能保證下標不超過workQueues.size; r是當前線程的局部變量,取這個值儘可能避免衝突;SQMASK是一個常量0x007e,第0bit位是0,這就保證了下標計算出來一定是偶數。
- (1) 判斷對應下標的workQueues的元素不爲空且pool的狀態正常;
- (2)加鎖 U.compareAndSwapInt(q, QLOCK, 0, 1)) ;
- (3)如果對應WorkQueue中任務隊列已初始化(不等於null)且未滿,則加入該隊列;
- (4)釋放鎖 U.compareAndSwapInt(q, QLOCK, 1, 0);
- (5) 如果添加成功,喚醒工作線程 sinalWork;
- (6)如果沒有成功添加,調用externalSubmit方法;
我們再看一下externalSubmit函數源碼如下:
public class ForkJoinPool extends AbstractExecutorService {
......
private void externalSubmit(ForkJoinTask<?> task) {
int r; // initialize caller's probe
if ((r = ThreadLocalRandom.getProbe()) == 0) {
ThreadLocalRandom.localInit();
r = ThreadLocalRandom.getProbe();
}
//筆者注:循環操作,每次循環的動作都非常小,符合條件就跳出來
for (;;) {
WorkQueue[] ws; WorkQueue q; int rs, m, k;
boolean move = false;
//筆者注: runState爲負數,則表示shutdown,終止線程池並跳出循環
if ((rs = runState) < 0) {
tryTerminate(false, false); // help terminate
throw new RejectedExecutionException();
}
//筆者注:pool未初始化,則初始化然後繼續循環
else if ((rs & STARTED) == 0 || // initialize
((ws = workQueues) == null || (m = ws.length - 1) < 0)) {
int ns = 0;
rs = lockRunState();
try {
if ((rs & STARTED) == 0) {
U.compareAndSwapObject(this, STEALCOUNTER, null,
new AtomicLong());
//筆者注:這裏保證workQueues是2的整數次方,方法巧妙,n經過一系列位運算
//變成了一個從第0位開始出現連續1的直到出現一個bit位是0,然後就一直是0,
//這樣最後再加1就變成了2的整數次方。
// create workQueues array with size a power of two
int p = config & SMASK; // ensure at least 2 slots
int n = (p > 1) ? p - 1 : 1;
n |= n >>> 1; n |= n >>> 2; n |= n >>> 4;
n |= n >>> 8; n |= n >>> 16; n = (n + 1) << 1;
workQueues = new WorkQueue[n];
ns = STARTED;
}
} finally {
unlockRunState(rs, (rs & ~RSLOCK) | ns);
}
}
//筆者注:如果workQueue不爲空,則加鎖嘗試添加。添加成功喚醒工作線程並跳出,否則繼續循環
else if ((q = ws[k = r & m & SQMASK]) != null) {
if (q.qlock == 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) {
ForkJoinTask<?>[] a = q.array;
int s = q.top;
boolean submitted = false; // initial submission or resizing
try { // locked version of push
if ((a != null && a.length > s + 1 - q.base) ||
(a = q.growArray()) != null) {
int j = (((a.length - 1) & s) << ASHIFT) + ABASE;
U.putOrderedObject(a, j, task);
U.putOrderedInt(q, QTOP, s + 1);
submitted = true;
}
} finally {
U.compareAndSwapInt(q, QLOCK, 1, 0);
}
if (submitted) {
signalWork(ws, q);
return;
}
}
move = true; // move on failure
}
// 筆者注:如果pool未被鎖,則創建一個WorkQueue,注意k在前面一個elseif賦值了一個偶數
else if (((rs = runState) & RSLOCK) == 0) { // create new queue
q = new WorkQueue(this, null);
q.hint = r;
q.config = k | SHARED_QUEUE;
q.scanState = INACTIVE;
rs = lockRunState(); // publish index
if (rs > 0 && (ws = workQueues) != null &&
k < ws.length && ws[k] == null)
ws[k] = q; // else terminated
unlockRunState(rs, rs & ~RSLOCK);
}
else
move = true; // move if busy
//筆者注:重新獲取下標
if (move)
r = ThreadLocalRandom.advanceProbe(r);
}
}
......
}
這裏是一個循環,每次循環依次判斷條件,符合條件就跳出來:
- (1)判斷runState是否爲負數,如果是負數,則表示shutdown,終止線程池並返回;
- (2)否則繼續判斷,如果pool未初始化,則初始化之後然後開始另一次循環從(1)開始判斷;
- (3)否則繼續判斷,計算一個偶數下標k,如果workQueues[k]不爲null,嘗試添加任務進去,成功則喚醒工作線程並返回;
- (4)否則繼續判斷,如果pool未被其它線程鎖住,則創建一個WorkQueue賦值給workQueues[k],注意k在(3)中已經賦予了一個偶數,然後開始另一次循環從(1)開始判斷;
- (5)否則,開始另一次循環從(1)開始判斷;
submit任務添加完成,又1個疑問沒揭開:爲什麼判斷任務隊列(workQueue.array)未滿用a.length - 1 > q.top - q.base,感覺不需要-1? 不過這不影響閱讀,也不影響整體對ForkJoin的理解。先記錄下來,未來可能整明白。
此外,ForkJoin框架的還可以通過ForkJoinTask.fork來添加任務,源碼如下:
public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
......
//如果是work線程調用fork則添加到work線程的本地隊列裏面,否則添加到commonPool池子裏面
public final ForkJoinTask<V> fork() {
Thread t;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
ForkJoinPool.common.externalPush(this);
return this;
}
......
}
2.3 運行任務
這一節主要說明工作線程是怎麼啓動的?主要是添加任務時調用的signalWork:
public class ForkJoinPool extends AbstractExecutorService {
......
final void signalWork(WorkQueue[] ws, WorkQueue q) {
long c; int sp, i; WorkQueue v; Thread p;
//筆者注:ctl的高16位是激活線程數-parallelism的反碼,最高位爲0表示激活線程還可以增加
while ((c = ctl) < 0L) { // too few active
//筆者注:ctl的第32位是非激活線程鏈的top線程,爲0表示沒有非激活線程
if ((sp = (int)c) == 0) { // no idle workers
//筆者注:ctl的47~32位是線程總數-parallelism的反碼,最高爲0表示總的線程還可以加
if ((c & ADD_WORKER) != 0L) // too few workers
tryAddWorker(c);
break;
}
if (ws == null) // unstarted/terminated
break;
if (ws.length <= (i = sp & SMASK)) // terminated
break;
if ((v = ws[i]) == null) // terminating
break;
//筆者注:以下邏輯是獲取非激活線程鏈的top線程然後激活,再激活之前要將下一個非激活線程(stackPred表示)放到ctl裏面
int vs = (sp + SS_SEQ) & ~INACTIVE; // next scanState
int d = sp - v.scanState; // screen CAS
long nc = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & v.stackPred);
if (d == 0 && U.compareAndSwapLong(this, CTL, c, nc)) {
v.scanState = vs; // activate v
if ((p = v.parker) != null)
U.unpark(p);
break;
}
if (q != null && q.base == q.top) // no more work
break;
}
}
......
}
- 先根據pool的控制字段ctl判斷是否需要添加新的worker線程,如果需要添加並返回
- 否則,找到非激活線程鏈的top線程並激活,主要在激活之前,先要將下一個非激活線程(stackPred指向)設置到ctl,使之成爲top線程
創建worker則是調用pool中的factory(ForkJoinWorkerThreadFactory)創建一個工作線程,並將其註冊到pool中,註冊過程如下:
public class ForkJoinPool extends AbstractExecutorService {
......
final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
UncaughtExceptionHandler handler;
wt.setDaemon(true); // configure thread
if ((handler = ueh) != null)
wt.setUncaughtExceptionHandler(handler);
WorkQueue w = new WorkQueue(this, wt);
int i = 0; // assign a pool index
int mode = config & MODE_MASK;
int rs = lockRunState();
try {
WorkQueue[] ws; int n; // skip if no array
if ((ws = workQueues) != null && (n = ws.length) > 0) {
int s = indexSeed += SEED_INCREMENT; // unlikely to collide
int m = n - 1;
//保證i是奇數 | 1
i = ((s << 1) | 1) & m; // odd-numbered indices
if (ws[i] != null) { // collision
int probes = 0; // step by approx half n
int step = (n <= 4) ? 2 : ((n >>> 1) & EVENMASK) + 2;
while (ws[i = (i + step) & m] != null) {
if (++probes >= n) {
workQueues = ws = Arrays.copyOf(ws, n <<= 1);
m = n - 1;
probes = 0;
}
}
}
w.hint = s; // use as random seed
w.config = i | mode;
//筆者注:下標賦值scanState,因爲worker線程最大值0x7fff+1,workQueues的size是其2倍,所以下標最大值0xffff(16bits)
w.scanState = i; // publication fence
ws[i] = w;
}
} finally {
unlockRunState(rs, rs & ~RSLOCK);
}
wt.setName(workerNamePrefix.concat(Integer.toString(i >>> 1)));
return w;
}
......
}
註冊實際上就是創建一個WorkQueue對象維護起來,這裏不多介紹,就注意一點worker線程本地隊列一定是ForkJoinPool.workQueues的奇數下標的元素。“i = ((s << 1) | 1) & m” 中位運算"| 1"就保證i是奇數,後面遞增的step是2的整數次方,所以i = i + step還是奇數。同時,下標索引賦值給WorkQueue的scanState變量,因爲worker線程最大值0x7fff+1,workQueues的size是其2倍,所以下標最大值0xffff(16bits)。
work線程創建完成直接start:
public class ForkJoinPool extends AbstractExecutorService {
......
private boolean createWorker() {
ForkJoinWorkerThreadFactory fac = factory;
Throwable ex = null;
ForkJoinWorkerThread wt = null;
try {
if (fac != null && (wt = fac.newThread(this)) != null) {
//筆者注:啓動工作線程
wt.start();
return true;
}
} catch (Throwable rex) {
ex = rex;
}
deregisterWorker(wt, ex);
return false;
}
......
}
線程啓動執行ForkJoinWorkerThread.run,然後又會調用ForkJoinPool.runWorker
public class ForkJoinWorkerThread extends Thread {
......
public void run() {
if (workQueue.array == null) { // only run once
Throwable exception = null;
try {
onStart();
pool.runWorker(workQueue);
} catch (Throwable ex) {
exception = ex;
} finally {
......
}
}
}
......
}
public class ForkJoinPool extends AbstractExecutorService {
......
final void runWorker(WorkQueue w) {
//筆者注:擴容w的任務隊列,此處第一次調用相當於初始化
w.growArray(); // allocate queue
int seed = w.hint; // initially holds randomization hint
int r = (seed == 0) ? 1 : seed; // avoid 0 for xorShift
for (ForkJoinTask<?> t;;) {
//筆者注:掃描竊取任務,如果竊取到則執行任務
if ((t = scan(w, r)) != null)
w.runTask(t);
else if (!awaitWork(w, r))
break;
r ^= r << 13; r ^= r >>> 17; r ^= r << 5; // xorshift
}
}
......
}
runWorker是最頂層的循環,線程一直循環直到終止。runWorker調用scan方法,scan就是工作竊取的核心實現,源碼如下:
public class ForkJoinPool extends AbstractExecutorService {
......
private ForkJoinTask<?> scan(WorkQueue w, int r) {
WorkQueue[] ws; int m;
if ((ws = workQueues) != null && (m = ws.length - 1) > 0 && w != null) {
int ss = w.scanState; // initially non-negative
for (int origin = r & m, k = origin, oldSum = 0, checkSum = 0;;) {
WorkQueue q; ForkJoinTask<?>[] a; ForkJoinTask<?> t;
int b, n; long c;
if ((q = ws[k]) != null) {
if ((n = (b = q.base) - q.top) < 0 &&
(a = q.array) != null) { // non-empty
long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
if ((t = ((ForkJoinTask<?>)
U.getObjectVolatile(a, i))) != null &&
q.base == b) {
if (ss >= 0) {
//筆者注:任務存在,競爭竊取任務,競爭成功返回竊取成果,如果隊列中任務還有剩,喚醒非激活線程
if (U.compareAndSwapObject(a, i, t, null)) {
q.base = b + 1;
if (n < -1) // signal others
signalWork(ws, q);
return t;
}
}
//筆者注:如果當前線程 未激活,嘗試激活非激活鏈的top線程(如果oldSum不爲0則再掃描一次並且會將oldSum設置爲0)
else if (oldSum == 0 && // try to activate
w.scanState < 0)
tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
}
if (ss < 0) // refresh
ss = w.scanState;
r ^= r << 1; r ^= r >>> 3; r ^= r << 10;
origin = k = r & m; // move and rescan
oldSum = checkSum = 0;
continue;
}
checkSum += b;
}
//筆者注:又掃描了一圈
if ((k = (k + 1) & m) == origin) { // continue until stable
//筆者注:如果checSum==oldSum 說明連續2圈掃描過程中各隊列的base都沒變(即沒有任務),
//設置線程未激活。然後繼續掃描,如果第3圈掃描到任務,則嘗試激活線程,如果第3圈還
//沒掃到且checkSum又沒變,那就返回null,嘗試讓線程park。
if ((ss >= 0 || (ss == (ss = w.scanState))) &&
oldSum == (oldSum = checkSum)) {
if (ss < 0 || w.qlock < 0) // already inactive
break;
int ns = ss | INACTIVE; // try to inactivate
long nc = ((SP_MASK & ns) |
(UC_MASK & ((c = ctl) - AC_UNIT)));
w.stackPred = (int)c; // hold prev stack top
U.putInt(w, QSCANSTATE, ns);
if (U.compareAndSwapLong(this, CTL, c, nc))
ss = ns;
else
w.scanState = ss; // back out
}
checkSum = 0;
}
}
}
return null;
}
......
}
- (1)掃描到任務且線程是激活的,並且競爭竊取任務(其它線程也可能掃描到)。競爭成功返回竊取成果,競爭失敗繼續掃描;
- (2)掃描到任務但線程是未激活的(可能此前連續2圈都沒掃描到任務),則嘗試release非激活線程鏈的top線程(如果oldSum==0,繼續掃描並將oldSum、checkSum設置爲0以便下次能夠嘗試release線程);
- (3)如果連續掃描pool.workQueues 兩圈都沒有任務,oldSum==checkSum 說明連續兩圈各隊列的base值相同(沒有被竊取過)、且隊列中當前沒有任務,那麼設置線程未激活。繼續掃描第3圈:
- 第3圈掃描到任務,就會進入(2) 因爲已經線程已經設置爲未激活了
- 第3圈未掃描到任務,如果checkSum還是未變,跳出循環返回null,讓線程park;如果checkSum變了,繼續第4圈重複第3圈的過程(此時oldSum已經是第3圈的checkSum)。
- 如果返回null,runWorker會嘗試park當前線程。
2.4 獲取結果
ForkJoinTask是Future的實現類,所以get方法可以獲取結果。不過,作爲ForkJoin框架的任務,有自己獨有的方法-join()
public abstract class ForkJoinTask<V> implements Future<V>, Serializable {
......
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();
}
private int externalAwaitDone() {
int s = ((this instanceof CountedCompleter) ? // try helping
ForkJoinPool.common.externalHelpComplete(
(CountedCompleter<?>)this, 0) :
ForkJoinPool.common.tryExternalUnpush(this) ? doExec() : 0);
if (s >= 0 && (s = status) >= 0) {
boolean interrupted = false;
do {
//筆者注:設置當前任務的信號bit位,當任務結束時就會調用notifyAll
if (U.compareAndSwapInt(this, STATUS, s, s | SIGNAL)) {
synchronized (this) {
if (status >= 0) {
try {
//等待該任務對象notify
wait(0L);
} catch (InterruptedException ie) {
interrupted = true;
}
}
else
notifyAll();
}
}
} while ((s = status) >= 0);
if (interrupted)
Thread.currentThread().interrupt();
}
return s;
}
private int setCompletion(int completion) {
for (int s;;) {
if ((s = status) < 0)
return s;
if (U.compareAndSwapInt(this, STATUS, s, s | completion)) {
//筆者注:如果狀態的信號位(第17位)是1,則notifyAll喚醒所有等待結果的線程
if ((s >>> 16) != 0)
synchronized (this) { notifyAll(); }
return completion;
}
}
}
......
}
join方法最終調用externalAwaitDone方法,externalAwaitDone設置任務的信號位爲1,然後調用wait等待task對象喚醒。當任務完成時調用setCompletion方法。setCompletion判斷如果信號位爲1(說明有線程等待結果),則喚醒線程(notifyAll)。get方法和join方法差別不大,主要是對異常的處理和線程中斷的處理不一樣,此處不做贅述。
好記性不如爛筆頭,隨時記錄當下的理解!