一起共讀,共同閱步。
零:序言
原始碼閱讀本就是要貫注全神、戒驕戒躁的沉浸式過程。我本著浮躁至極的心態,單刀直入,從入口方法先殺入“敵軍”內部,讓大家短時間內享受到最大的學習成就感,然後再橫向鋪開,帶大家一窺原始碼的究竟。有不對之處,輕噴、指出。
java1.5引入的執行緒池的標準類,ThreadPoolExecutor。
我們通常是通過Executors這個工廠類來建立具體的例項,如:
Executors.newCachedThreadPool(...)
Executors.newScheduledThreadPool(...)
複製程式碼
前者建立的就是我們要講的ThreadPoolExecutor例項。後者是有延遲功能的執行緒池,ScheduledThreadPoolExecutor,有機會再講吧。ThreadPoolExecutor
這個執行緒池例項,內部維護了一個執行緒的集合,用來存放執行緒;有一個存放待執行任務的佇列,在池內執行緒數達到最大值時,任務就暫時入隊,等待執行緒取走執行。所以,目前來看,ThreadPoolExecutor
的結構如下:
零點一:ThreadPoolExecutor原始碼閱讀思維導圖
我會先列一下該原始碼涉及到的重要的邏輯方法,然後按使用時通常的呼叫順序,挨個講解,最後合併總結。
execute
:執行任務方法,內部封裝了新建執行緒、任務入隊等重要邏輯addWorker
:新建執行緒方法getTask
:從任務佇列內獲取一個任務runWorker
:池內執行緒的主迴圈邏輯。提醒一下,多執行緒都會呼叫這同一個方法,所以尤其注意同步問題。
零點五:ThreadPoolExecutor內的關鍵變數解釋
workQueue
[BlockingQueue
],任務佇列,是一個BlockingQueue物件,執行緒安全ctl
[Integer
],記錄了執行緒池的執行狀態值跟池內的執行緒數workers
[HashSet<Worker>
],具體存放執行緒的set
物件corePoolSize
[volatile int
],執行緒池核心執行緒數配置,低於這個數值時,新進來的任務一律以新啟動執行緒處理
一:執行緒池狀態ctl
基礎知識準備
ThreadPoolExecutor例項代表一個執行緒池,相應地,這個池子就有一些執行狀態,以及池子內執行緒數量的配置。這兩者的實現如下:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
複製程式碼
執行緒池狀態跟池內執行緒數的統計都記錄在ctl
這個AtomicInteger
(它是誰,先自己查吧。後面估計會專門寫下,這裡只要記住它的加減是執行緒安全的就好)中。具體實現是:
java內一個整型量是32位,這裡AtomicInteger
也是。原始碼作者將ctl
的32位中高3位用來記錄執行緒池狀態,低29位用來記錄執行緒數量。
驗證來看,COUNT_BITS
的值是29,方法
runStateOf(int c) {return c & ~CAPACITY;}
這裡的c
就是ctl
變數,而CAPACITY
就是一個mask面紗,用來從 ctl
中提取上面兩個變數的,它是這樣的:
所以,runState
就是ctl
與取反後的 CAPACITY
相與,也就是隻有高4位有效,正好對應執行緒池狀態的記錄位。
所以,各種狀態下,ctl
的值如下:
RUNNING
:1001 x(28個),-1,最高位是符號位,這個<<
位移操作是忽略符號位的位移
SHUTDOWN
:0000 x(28), 0
STOP
: 0001 x(28), 1
TIDYING
: 0010 x(28), 2
TERMINATED
: 0011 x(28), 3
二:入口函式execute(Runnable command)
用過執行緒池的人應該都用過這個入口函式,它就是用來將一個Runnable
任務體放入執行緒池,下面讓我們來具體看看它的邏輯(程式碼塊沒法高亮了,大家看下程式碼段中註釋的翻譯部分):
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
* 主要的邏輯註釋,在這
* 1. If fewer than corePoolSize threads are running, try to
* 如果當前池內執行緒數小於corePoolSize,那就嘗試去新建一
* start a new thread with the given command as its first
* 條執行緒,傳進來的command引數作為該執行緒的第一個任務
* task. The call to addWorker atomically checks runState and
* 體。呼叫addWorker函式會自動檢查執行緒池的狀態和池內活躍的執行緒數
* workerCount, and so prevents false alarms that would add
* 如果在不該或不能新建執行緒時新建了,那不會丟擲異常,會返回false
* threads when it shouldn't, by returning false.
*
* 即使任務體成功入隊,我們仍需要再去檢查一遍,我們是否應該是新建執行緒而不是入隊,
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (因為可能自第一次檢查後,原池內活躍的執行緒
* (because existing ones died since last checking) or that
* 又死掉啦)又或者執行緒池的狀態發生了變化
* the pool shut down since entry into this method. So we
* 所以我們去二次檢查池內狀態,若執行緒池狀態變為停止,就去回滾,或者執行緒池此時一個活躍執行緒都沒有的話,
* recheck state and if necessary roll back the enqueuing if
* 新建一個執行緒去執行任務體。(PS:因為ThreadPoolExecutor
* stopped, or start a new thread if there are none.
* 主要是通過addWorker來建立執行緒,所以,如果池內一個活躍執行緒都沒有,
* 這時我們任務體入隊了,也沒有執行緒去跑...當然為什麼只檢查一遍?我是想,可
* 能就只是作者單純地在這裡想檢查一遍,稍微確保下。因為即使這個二次檢查
* 沒問題,後續的,到池內執行緒確切地去跑這個任務體之前的程式碼,每一行
* 程式碼,都仍有發生這種情況的可能。這,就是多執行緒...)
* 3. If we cannot queue task, then we try to add a new
* 如果我們任務體入隊失敗,那我嘗試新建執行緒,如果還失敗
* 那就說明執行緒池已經被shutdown了,或者整個池子已經滿了,那我們
* 就去拒絕這個任務體。這個拒絕,就會用到所謂的RejectPolicy物件
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
// 獲取ctl物件
int c = ctl.get();
// 如果池內活躍執行緒數小於corePoolSize
if (workerCountOf(c) < corePoolSize) {
// 新建執行緒,第二個引數true可以先忽略
if (addWorker(command, true))
return;
// 新建執行緒失敗,那我們獲取最新的執行緒池狀態變數ctl
c = ctl.get();
}
// 如果當前執行緒池仍在執行而且任務體入隊成功。
// (workQueue就是ThreadPoolExecutor具體的任務佇列。
// 而這裡就是我們上面註釋提到的那段二次檢查的邏輯)
if (isRunning(c) && workQueue.offer(command)) {
// 二次檢查。獲取最新的執行緒池狀態欄位
int recheck = ctl.get();
// 如果執行緒不在執行狀態 並且也成功把入隊的任務體刪除了
// 那就菜哦用拒絕策略來拒絕
if (! isRunning(recheck) && remove(command))
reject(command);
// 或者,線上程池內活躍的執行緒數為0時,新建一個執行緒
// 這裡傳參跟上面不一樣,先忽略。記錄這個新啟動一個執行緒就夠了
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 如果上面的If失敗了,就嘗試新啟動執行緒,啟動失敗了,那說明
// 上面的失敗,是isRunning造成的,所以拒絕任務體。啟動成功了,那就是成功了。
else if (!addWorker(command, false))
reject(command);
}
複製程式碼
這裡涉及到ThreadPoolExecutor
執行緒池增加執行緒的一個判斷邏輯:
每當ThreadPoolExecutor.execute
執行一個任務時,先判斷corePoolSize
,當池內執行緒數小於這個時,直接新增執行緒,若大於這個,則向workQueue
任務佇列入隊,佇列滿了時,則以maximumPoolSize
為界開始繼續新建執行緒,當超過maximumPoolSize
時,就採用最後的RejectPolicy進行拒絕處理。
三: 函式addWorker(Runnable firstTask, boolean core)
這個函式主要邏輯是新啟動一個執行緒,firstTask
是新啟動執行緒的第一個任務,可以為null
,為null
時,就是單純地啟動一個執行緒,記得我們之前在execute(Runnable command)
方法中,線上程池內沒有有效執行緒時,呼叫firstTask
為null
的方法來啟動一條執行緒。
第二個引數core
是用來辨別,啟動一個新執行緒時,是以corePoolSize
這個執行緒數配置量來作為限制,還是以maximumPoolSize
這個執行緒數配置量作為限制。
看下原始碼(邏輯主要放註釋裡了):
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
// 獲取到執行緒池的執行狀態
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
// 檢查任務佇列在它必要的時候才能為空。這裡
// 只能直接翻譯下,具體的,if裡的判斷邏輯也能推斷出來
// 但是目前我也不能確定說出在這種情況下要false退出的
// 原因。如果想搞清它,可能只能徹底把執行緒池的執行狀態、
// 執行緒池內的執行緒數、任務佇列內的任務數三者所有可能的情況的
// 前提下才能確定。這裡待大神指出來了。
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
// 第二個引數的作用就在這裡產生了!
// 這裡在確保池內執行緒數不超過ctl極限CAPACITY
// 以及不超過相應的xxxPoolSize的情況下,通過
// CAI操作去給執行緒數加1,成功了,則跳出retry標記後
// 的迴圈。至於CAI是什麼?先記住它是執行緒安全的給數值+1的操作就好
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 到此位置,執行緒池內的執行緒數標記欄位已經加1了
// 接下來的,就是具體新增一個執行緒的操作了
//
// 這裡就不可避免的涉及到了ThreadPoolSize中的
// Worker這個內部類了,這個類就是具體的ThreadPoolSize
// 內部用來代表一個執行緒的封裝物件,他封裝了一個執行緒例項
// ,用來跑具體的任務;封裝了一個Runnable 例項,代表具體的任務;
// 同時,它繼承、實現了AQS(AbstractQueuedSynchronizer)跟Runnable,所以,這個
// Worker例項可以理解成一個小勞工,有自己的執行執行緒,有
// 自己的具體的執行任務體,同時,自己也有同步機制AQS。
// 這裡涉及到AQS,大傢伙就暫且理解成AQS賦予Worker同步的性質即可(呼叫AQS的方法就能實現)
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 初始化一個Worker勞工,同時指定給他的任務。這個任務
// 可以為null,空。表示什麼也不做。同時,初始化的時候,也會
// 初始化Worker體內的執行緒物件,這條執行緒的物件的啟動,是
// 在worker物件的Runnable.run實現方法裡
w = new Worker(firstTask);
final Thread t = w.thread;
// 這個mainLock是ThreadPoolExecutor用來同步對
// workers執行緒佇列的CRUD操作的
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
// 當執行緒池處於RUNNING狀態,則可以繼續操作;
// 或者當執行緒池處於SHUTDOWN,但是firstTask 為null
// 也就是說,這裡是為了增加一個執行緒的,所以,也可以放行
// 因為SHUTDOWN狀態,是允許啟動執行緒將任務佇列內的任務跑完的
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 將新生成的執行緒勞工加入到執行緒集合中
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 如果執行緒勞工新增成功,則啟動它
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
// 如果執行緒啟動失敗,則執行回滾操作
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
複製程式碼
四: Worker
物件的執行緒start()
這裡講述的方法是接上面addWorker
時,成功呼叫的t.start()
,這裡啟動了Worker
封裝的執行緒。這個執行緒是Worker
建構函式裡生成的,如下:
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
// 這裡設定了AQS的state,用來抑制interrupt直到
// runWorker方法
setState(-1); // inhibit interrupts until runWorker
// 這裡傳遞了執行緒的任務體,可以為null
this.firstTask = firstTask;
// 初始化執行緒時,給執行緒指定了worker例項自身這個Runnable,因此,執行緒在start後,
// 就是在執行worker當前例項自身的run方法
this.thread = getThreadFactory().newThread(this);
}
複製程式碼
看完上面程式碼的註釋,接著看worker
例項自身的run
方法
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
複製程式碼
可以看到這裡呼叫了runWorker
方法,傳參是worker
自身。runWorker
是同一個ThreadPoolExecutor
例項的方法,所以,執行緒池例項下的所有Worker
執行緒都是在跑這同一個runWorker
方法。
五: 執行緒池的迴圈體方法runWorker(Worker worker)
/**
* Main worker run loop. Repeatedly gets tasks from queue and
* executes them...
*/
final void runWorker(Worker w) {
// 這裡獲取到執行緒物件,其實就是引數Worker物件內封裝的Thread物件。
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
// allow interrupts,允許Interrupt打斷,記得Worker物件的建構函式嘛?
// 建構函式一開始就呼叫了setState(-1)去抑制interrupt。這裡就是去釋放它。
// 當然,這裡具體的抑制interrupt的含義,要結合AQS來了解了,我後面再加吧。
w.unlock();
boolean completedAbruptly = true;
try {
// 如果Worker中的firstTask物件不是空的,則
// 直接跑它;若不然,呼叫getTask從佇列中獲取一條任務來執行。這裡
// 會一直while迴圈,所以worker們在任務佇列中有任務時
// 會一直在這個runWorker中迴圈while取任務執行
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
// 這裡有一個很巧妙的判斷邏輯,這段程式碼也主要是實現了
// 上面英文註釋中的邏輯,但是可能比較難理解,稍等
// 在後面我會專門講一下
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
// 任務體的前置函式,現在預設是空函式
beforeExecute(wt, task);
Throwable thrown = null;
try {
// 任務體的執行函式
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x;
throw new Error(x);
} finally {
// 後置函式
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
// 這個引數記錄worker執行是否是被打斷的,如果不是,程式碼
// 會安全地走到這裡,然後置欄位為false。
// 否則,異常情況下就直接跳到finally中了,值仍為初始化時的true
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
複製程式碼
這段原始碼上方我放了一段註釋,翻譯過來就是:
worker
物件的主要Loop迴圈體。從佇列(workQueue
)中獲取任務體,然後執行。
六: 從任務佇列獲取任務的getTask
函式
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
// 這個for(;;)也是個無限迴圈的實現,它比while(true)的好處是
// 在位元組碼層面上,它比while(true)少兩行位元組碼程式碼,所以
// 效率更高
for (;;) {
// 獲取執行緒池最新狀態
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
// allowCoreThreadTimeOut表示coreThread,其實是判斷
// 執行緒數在這個coreThreadPoolSize範圍內時,執行緒是否可以超時。
// 這裡的判斷邏輯也很巧妙,如果allowxxxTimeOut為true,coreThread
// 可以超時,則 || 後面判斷coreThread的邏輯也就無所謂了,是吧。
// 但如果allowxxxxTimeOut為false,coreThread不允許超時,
// 則需要去判斷在判斷的執行緒是否實在coreThread範圍內,是的話,
// 則最終結果也為false,符合coreThread不能超時的邏輯;如果大於,
// 則說明當前方法的執行緒不是在coreThread,
// 注意去理解這個是不是coreThread這個概念
// 所以,timed為true,也就是可以超時,符合邏輯
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 這裡我把程式碼格式化了下,方便大家去看
// 這裡的判斷就是,在符合了一些邏輯後,就去直接
// wokerCount減一,代表當前這個woker就直接幹掉了,
// 而在方法內返回null這個邏輯,在呼叫getTask的程式碼處
// 確實也是去幹掉當前的worker例項。但是,woker不能
// 瞎幹掉,必須要確保執行緒池能正常產生作用,這個正常作用
// 的實現,要麼就是幹掉當前的worker還剩下至少一個,
// 要麼就是任務佇列空了,這個邏輯就在(wc > 1 || workQueue.isEmpty)
// 實現了。再來看 && 之前,在當前執行緒數大於
// maximumPoolSize限制時,或者當前woker可以超時,
// 即timed為true,同時,上一次獲取任務體時也超時了(timedOut)
// 則,當前的worker就幹掉。這段邏輯有一個timedOut
// 判斷,即上一次當前worker獲取任務體時就超時了。
// 我猜測,加這個邏輯,可能就是純粹的統計學上的效率
// 提高。當然,歡迎更多想法。
//
// 在符合上述條件後,CAS操作來減少workerCount數
// 再返回null,去幹掉當前worker例項。
if (
(wc > maximumPoolSize || (timed && timedOut))
&&
(wc > 1 || workQueue.isEmpty())
) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 根據當前的worker是否可以超時,呼叫BlockingQueue
// 的不同方法來獲取任務體。
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
// 獲取到任務體,則返回
if (r != null)
return r;
// 超時了,記錄標記位
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
複製程式碼
七: 截止目前,所有的任務入隊、執行緒迴圈、取任務執行任務的邏輯就已經都看完了
這裡我們總結下:
ThreadPoolExecutor的實際邏輯圖
workers
執行緒集合中的Worker
物件,在runWorker
中迴圈自workQueue
中獲取Runnable
任務體進行執行。對workers
執行緒集合的訪問要經過mainLock
這個鎖。