這是在部門做技術分享的時候寫的一篇學習筆記,順便貼在這裏給大家看看,歡迎指出錯誤,共同學習
ForkJoin是什麼
ForkJoin是用於並行執行任務的框架, 是一個把大任務分割成若干個小任務,最終彙總每個小任務結果後得到大任務結果的框架。Fork就是把一個大任務切分爲若干子任務並行的執行,Join就是合併這些子任務的執行結果,最後得到這個大任務的結果。
數據結構
關鍵調用圖
源碼解析
1、pool屬性
// Instance fields
volatile long ctl; // 控制中心:非常重要,看下圖解析
volatile int runState; // 負數是shutdown,其餘都是2的次方
final int config; // 配置:二進制的低16位代表 並行度(parallelism),
//高16位:mode可選FIFO_QUEUE(1 << 16)和LIFO_QUEUE(1 << 31),默認是LIFO_QUEUE
int indexSeed; // 生成worker的queue索引
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
2、控制中心:ctl
3、WorkQueue
// Instance fields
volatile int scanState; // 負數:inactive, 非負數:active, 其中奇數代表scanning
int stackPred; // sp = (int)ctl, 前一個隊列棧的標示信息,包含版本號、是否激活、以及隊列索引
int nsteals; // 竊取的任務數
int hint; // 一個隨機數,用來幫助任務竊取,在 helpXXXX()的方法中會用到
int config; // 配置:二進制的低16位代表 在 queue[] 中的索引,
// 高16位:mode可選FIFO_QUEUE(1 << 16)和LIFO_QUEUE(1 << 31),默認是LIFO_QUEUE
volatile int qlock; // 鎖定標示位:1: locked, < 0: terminate; else 0
volatile int base; // index of next slot for poll
int top; // index of next slot for push
ForkJoinTask<?>[] array; // 任務列表
4、externalPush || externalSubmit
final void externalPush(ForkJoinTask<?> task) {
WorkQueue[] ws; WorkQueue q; int m;
//我們以前常用的Random,在併發下,多個線程同時計算種子需要用到同一個原子變量。
//由於更新操作使用CAS,同時執行只有一個線程成功,其他線程的大量自旋造成性能損失,ThreadLocalRandom繼承Random,對此進行了改進。
//ThreadLocalRandom運用了ThreadLocal,每個線程內部維護一個種子變量,多線程下計算新種子時使用線程自己的種子變量進行更新,避免了競爭。
int r = ThreadLocalRandom.getProbe();
int rs = runState;
// 外部提交的task,肯定會到偶數位下標的隊列上
// SQMASK = 0x007e = 1111110,任何數和 SQMASK 進行 & 運算 都會是偶數
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)) {
ForkJoinTask<?>[] a; int am, n, s;
if ((a = q.array) != null &&
(am = a.length - 1) > (n = (s = q.top) - q.base)) {
int j = ((am & s) << ASHIFT) + ABASE;
//把 task 放到隊列的 top端
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);
}
5、righsterWorker
final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
//......
if ((ws = workQueues) != null && (n = ws.length) > 0) {
int s = indexSeed += SEED_INCREMENT; // unlikely to collide
int m = n - 1;
// worker的queue肯定放在pool中的queue[]中的奇數下標
// m = ws.lenght - 1, ws.lenght 肯定是偶數,則m 肯定是奇數
// 1的二進制位:00000001, 所以任何數 "|" 1 都是奇數
// 所以 奇數 & 奇數 , 1&1 = 1,所以i肯定是奇數
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;
// 如果下標已經有隊列,則重新生成奇數下標
// step肯定爲偶數:EVENMASK:0xfffe:1111111111111110
// 所以 奇數+偶數,奇偶性不變
while (ws[i = (i + step) & m] != null) {
if (++probes >= n) {
workQueues = ws = Arrays.copyOf(ws, n <<= 1);
m = n - 1;
probes = 0;
}
}
}
//...
}
//......
}
6、scan:
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
// k = r & m 。 r是一個隨機數,m 是 隊列數組長度 - 1;用於定位去哪個 隊列 竊取 task
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) {
// 如果有還沒執行的task,嘗試竊取隊列q 中的base下標的 task。 即FIFO
// i: 在內存中,b下標對應的對象的偏移值。 a.length - 1 的二進制位 永遠是 0[1...]s,所以 (a.length - 1) & b = b,主要是保證了b不會越界
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) {
// ss 是小偷的 scanState,大於0代表當前的worker是激活的
if (ss >= 0) {
// 把 task 從 隊列中取出來,然後隊列的base+1,如果被竊取的隊列中有多於1個的task,則嘗試喚醒其他的worker
if (U.compareAndSwapObject(a, i, t, null)) {
q.base = b + 1;
if (n < -1) // signal others
signalWork(ws, q);
return t;
}
}
// ss小於0代表當前的worker是未激活的,並且當前是第一次掃描,這時候嘗試激活worker
// oldSum: 上一次遍歷週期的 base 值的和。
// (int) c : 可以拿到當前棧頂的空閒worker。sp = (int) c
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;
}
// 每次沒有竊取到task的時候,都會k+1(k值不會超過m),當k遍歷了一圈還沒有steal到任務,則當前小偷worker是過剩的,則inactive這個小偷worker
if ((k = (k + 1) & m) == origin) { // continue until stable
// oldSum == (oldSum = checkSum) 實際上就是 oldSum == checkSum , oldSum = checkSum
// oldSum == checkSum 是判斷 這個週期和上個週期 的base和是否一直,如果一直, 說明base可能沒有變過
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)));
// 維護 隊列的 stack,可以指向前一個棧頂的隊列
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;
}
7、signalWork
final void signalWork(WorkQueue[] ws, WorkQueue q) {
long c; int sp, i; WorkQueue v; Thread p;
// AC是負數,所以 active worker不足
while ((c = ctl) < 0L) { // too few active
// sp:第一位是0,沒有版本號,沒有inactive的worker
if ((sp = (int)c) == 0) { // no idle workers
//tc: tc不爲0,就是代表 total worker - parallelism < 0, 所以需要添加worker
if ((c & ADD_WORKER) != 0L) // too few workers
tryAddWorker(c);
break;
}
if (ws == null) // unstarted/terminated
break;
// 取棧頂的worker,如果下標已經越界或queue爲null,線程池都是終止了
if (ws.length <= (i = sp & SMASK)) // terminated
break;
if ((v = ws[i]) == null) // terminating
break;
// 新的scanState,版本+1,設置狀態爲激活,INACTIVE = 1 << 31,~INACTIVE = 01111111....
int vs = (sp + SS_SEQ) & ~INACTIVE; // next scanState
// 確認 worker的 sp沒有變化
int d = sp - v.scanState; // screen CAS
// 生成新的 ctl,(UC_MASK & (c + AC_UNIT))設置 高32位, (SP_MASK & v.stackPred)設置低32位
long nc = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & v.stackPred);
if (d == 0 && U.compareAndSwapLong(this, CTL, c, nc)) {
//激活worker
v.scanState = vs; // activate v
if ((p = v.parker) != null)
U.unpark(p);
break;
}
//當前queue沒有task 需要執行了,則停止signal
if (q != null && q.base == q.top) // no more work
break;
}
}
parallelStream的注意事項
- parallelStream.forEach是不保證順序的,如果要保證順序正確,則使用 forEachOrdered。
- parallelStream實際上使用的是多線程的方式,所以在運行的業務邏輯裏面要注意線程安全的問題