分析線程池的底層原理,有問題煩請批評指正。
execute()方法
提交任務command到線程池執行。
public void execute(Runnable command) {
// 0. 如果任務爲空,拋出NPE
if (command == null)
throw new NullPointerException();
// 獲取ctl屬性=線程狀態+線程數
int c = ctl.get();
// 1. 如果當前池中線程數量小於核心線程數
if (workerCountOf(c) < corePoolSize) {
// 向workers新增核心線程執行command任務
if (addWorker(command, true))
return;
// 添加線程失敗
c = ctl.get();
}
// 2. 如果池是RUNNING狀態,並且可以添加任務到阻塞隊列
if (isRunning(c) && workQueue.offer(command)) {
// 二次校驗:可能在添加任務到隊列後,
// 不再需要添加一個線程(過程中有線程死亡)或者線程池關閉,池狀態發生改變。
int recheck = ctl.get();
// 2.1 如果線程池已關閉,則刪除隊列任務【巧妙的寫法 !false && remove()】
if (!isRunning(recheck) && remove(command))
// 執行拒絕策略
reject(command);
// 2.2 否則如果池中線程數爲0,則新增一個線程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 3. 如果隊列已滿,則新增線程;如果新增失敗,就執行拒絕策略。
else if (!addWorker(command, false))
reject(command);
}
上面代碼執行的步驟使用文字來敘述一下。
1 如果當前工作線程數量,小於核心線程的數量,就嘗試添加一個新線程,並把傳入的command作爲新添加的線程的第一個任務,添加成功就返回。調用addWorker方法會以原子方式檢查runState和workerCount並且通過返回false來避免在假喚醒的時候添加線程。
2 如果當前線程池正在運行並且任務可以成功加入到隊列中,我們仍然需要再次檢查線程池(檢查線程池的運行狀態,當前線程池中的工作線程數量)。爲什麼要再次檢查呢?
當任務被添加到了阻塞隊列前,線程池處於RUNNING 狀態,但如果在添加到隊列成功後,線程池進入了SHUTDOWN 狀態或者其他狀態,這時候是不應該再接收新的任務的,所以需要把這個任務從隊列中移除,並且拒絕該任務。同樣,在沒有添加到隊列前,可能有一個有效線程,但添加完任務後,這個線程可能因爲閒置超時或者異常被幹掉了,這時候需要創建一個新的線程來執行任務。
2.1 如果線程池狀態線程池是SHUTDOWN了,並且能成功的把任務從隊列中移除,就拒絕任務。
2.2 如果任務加入到隊列了,但是當前沒有工作線程就添加一個線程。
3 如果我們不能把任務加入隊列,那麼我們嘗試添加一個新線程,如果添加失敗(線程池已經shut down 或者已經飽和了),就拒絕任務。
所謂拒絕,就是調具體拒絕策略的rejectedExecution()方法。
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
addWorker方法
其中addWorker是execute()方法的核心方法,用於原子校驗線程池狀態和線程數,並新增線程。
private boolean addWorker(Runnable firstTask, boolean core) {
// 跳轉標籤
retry:
// 外層循環
for (;;) {
// 獲取ctl屬性與線程池狀態
int c = ctl.get();
int rs = runStateOf(c);
// 1. 線程池狀態校驗,僅在必要時檢查隊列是否爲空。
// 返回false總結
// 1.1 線程池是STOP、TIDYING、TERMINATED
// 2.2 線程池是SHUTDOWN,並且任務不爲null或阻塞隊列爲空
if (rs >= SHUTDOWN && // 線程池非RUNNING狀態
! (rs == SHUTDOWN && // 特殊情況:池狀態爲SHUTDOWN,任務爲null,工作隊列不爲空
firstTask == null &&
! workQueue.isEmpty()))
return false;
// 內層循環,CAS增加線程個數
for (;;) {
int wc = workerCountOf(c);
// 2. 當前線程個數超過上限,返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 3. CAS成功增加線程數,直接退出外層循環
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // 再次讀取ctl值
// 如果CAS增加線程失敗,校驗線程池狀態是否發生改變
// 4. 如果池狀態改變,跳到外層循環,重新獲取池狀態;
if (runStateOf(c) != rs)
continue retry;
// 未改變,則內層循環再次進行CAS操作
}
}
// 此時完成CAS操作,線程數增加
// 5. 添加worker的邏輯
boolean workerStarted = false; // 標記工作線程是否啓動
boolean workerAdded = false; // 標記worker是否添加到workers
Worker w = null; // 工作線程
try {
// 創建worker
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
// 此時,線程工廠成功創建了一個線程
final ReentrantLock mainLock = this.mainLock; // 全局獨佔鎖-mainLock
// 加鎖:實現worker添加操作同步
mainLock.lock();
try {
// 再次檢查線程池狀態。ThreadFactory失敗或者獲取鎖前shutdown,退出
int rs = runStateOf(ctl.get());
// 線程池狀態爲RUNNING,或者池狀態爲SHUTDOWN,並且任務爲null時
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// 如果線程已經啓動,拋出異常
if (t.isAlive())
throw new IllegalThreadStateException();
// 添加worker到線程池workers線程集合
workers.add(w);
int s = workers.size();
// 改變最大線程數,池大小動態變化
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true; // 更新任務添加標識
}
} finally {
// 解除mainLock鎖
mainLock.unlock();
}
// 任務添加後,啓動線程
if (workerAdded) {
t.start();
workerStarted = true; // 更新線程啓動標識
}
}
} finally {
// 如果線程啓動失敗,回滾worker創建過程
if (! workerStarted)
addWorkerFailed(w);
}
// 返回任務是否成功啓動
return workerStarted;
}
檢查根據當前池狀態和給定的邊界(核心或者最大)是否可以添加新worker。
代碼主要分爲兩部分,一部分是通過CAS增加線程數,另一部分是加鎖添加任務到workers,並啓動線程。
如果線程池關閉、線程工廠創建失敗(線程工廠返回空,或異常(線程創建時OOM)),會返回false,並回滾上面的操作。
回滾過程,包括
- 從workers刪除任務;
- CAS減少線程數
- 重新校驗終止,避免該worker的存在阻礙了終止。
private void addWorkerFailed(Worker w) {
// 獲取全局鎖
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
// 刪除worker任務
workers.remove(w);
// CAS較少線程數
decrementWorkerCount();
// 嘗試終止
tryTerminate();
} finally {
mainLock.unlock(); // 解鎖
}
}
Woker嵌套類–Worker線程類
Worker類實現Runnable接口,用來具體承載任務;繼承AQS,自己實現不可重入的獨佔鎖。鎖狀態state有3個:-1表示創建時狀態,0表示鎖未獲取狀態,1表示鎖已獲取狀態。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable
{
// 執行當前任務的線程,如果爲null說明線程工廠添加失敗
final Thread thread; // final修飾
// 要執行的任務,可能爲null
Runnable firstTask;
// 每個線程的任務計數器,標記當前線程執行了多少個任務
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // runWorker執行前禁止中斷
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this); // 創建一個線程
}
// Worker實現run方法
public void run() {
runWorker(this);
}
}
worker在run方法中調用了ThreadPoolExecutor的runWorker方法,並傳入worker實例自身。
runWorker
runWorker執行前禁止中斷,是因爲shutdownNow()方法中在中斷Worker時會對state做校驗,state>=0纔會去中斷線程。
inal void runWorker(Worker w) {
// 當前線程即worker中的線程
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // 設置state爲0,運行中斷
boolean completedAbruptly = true; // 快速結束標識
try { // 第1層trycatch
// 校驗firstTask和阻塞隊列中任務是否爲空
while (task != null || (task = getTask()) != null) {
w.lock(); // 加鎖
// 如果調用了shutdownNow()方法,池是STOP,確保線程被中斷
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt(); // 中斷線程
try { // 第2層trycatch
// 執行任務前,執行鉤子方法
beforeExecute(wt, task);
Throwable thrown = null;
try { // 第3層trycatch
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; // 清理worker中的任務,下次循環獲取新任務
w.completedTasks++;
w.unlock();
}
} // while循環結束
completedAbruptly = false;
} finally {
// 清理worker
processWorkerExit(w, completedAbruptly);
}
}
- while 循環,如果Worker的firstTask不爲null,或者getTask() 從隊列中獲取任務不爲null。注意從隊列中獲取任務可能會阻塞。如果Worker能夠取到任務,就不停的執行,這就是爲什麼線程可以複用不退出的原因。
任務分爲兩種,一種是創建Woker的那個firstTask,另一種是阻塞隊列中的任務。
如果兩種任務均爲null,或者線程池狀態改變,completedAbruptly快速結束標識會保持爲true,進行worker清理操作。 - 在適當的時候中斷Worker的工作線程wt。
- 執行任務。
執行任務時加鎖,避免任務運行時,被其他線程調用shutdown中斷。除非線程池是STOPPING狀態,否則不會中斷線程。
beforeExecute()方法在第2層trycatch內,如果拋出異常退出,任務不再執行,只會執行後面的兩層finally操作。completedAbruptly值仍爲true,清理worker線程。
任何異常,都會導致清理線程。 - 如果沒有可執行的任務,或者異常終止則Worker停止工作,Worker的工作線程也就結束了。
processWorkerExit()方法用來清理將要死亡的線程。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 1. 如果是突然退出,CAS減少線程數
if (completedAbruptly)
// 只在此處和getTask()方法中調用!!
decrementWorkerCount();
// 2. 統計線程池完成的線程數,並從工作線程集中移除當前Worker
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
// 3. 試圖終止線程池
tryTerminate();
// 獲取ctl屬性
int c = ctl.get();
// 4. 如果線程池未終止,判斷是否新增worker
// 線程池狀態是RUNNING或者SHUTDOWN
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
// 如果worker不是突然退出
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
// 4.1 如果是突然退出,直接添加新worker
// 4.2 如果正在運行的線程少於corePoolSize,則新增worker
// 4.3 如果此時阻塞隊列不爲空,但是工作線程數爲0,則新增worker
addWorker(null, false);
}
}
getTask()方法用來從阻塞隊列中獲取任務
// 執行阻塞或者定時等待任務
private Runnable getTask() {
boolean timedOut = false; // 最後的poll操作是否超時
// 死循環
for (;;) {
int c = ctl.get();
int rs = runStateOf(c); // 線程池狀態
// 1. 循環退出條件1:線程狀態
// 1.1 線程池是STOP、TIDYING、TERMINATED狀態
// 1.2 線程池非RUNNING狀態,並且任務隊列爲空,返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
// 減少workerCount數
decrementWorkerCount();
return null;
}
// 當前池中woker數
int wc = workerCountOf(c);
// 是否結束當前woker
// allowCoreThreadTimeOut默認爲false(核心線程即使空閒,仍然可以存活,即不使用超時)
// coreThread能夠超時,或者當前woker數大於corePoolSize時,當前woker可以超時
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 2. 循環退出條件2:線程數
// 2.1 worker的數量大於maximumPoolSize
// 2.2 worker等待從隊列中獲取任務時超時
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
// 減少worker數,並返回null
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ? // 是否可超時
// 如果可以超時,使用poll方法,在keepAliveTime時間內獲取任務【keepAliveTime超時的使用】
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
// 不可以超時,則一直等待直到獲取到任務爲止【核心線程不退出的原因】
workQueue.take();
if (r != null)
// 成功獲取到任務,直接返回
return r;
// 走到這一步,說明從任務隊列中獲取任務超時了。
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
方法只有兩種結果,一種是成功獲取到任務,另一種是當前worker必須退出時返回null,此時減少wokerCount數。
當前worker在遇到以下原因時必須停止返回null。
- worker的數量大於maximumPoolSize
- 線程池大於等於STOP狀態
- 線程池是SHUTDOWN狀態,並且阻塞隊列沒有任務
- worker等待從隊列中獲取任務時超時【着重理解】
最最核心的代碼
Runnable r = timed ? // 是否可超時
// 如果可以超時,使用poll方法,在keepAliveTime時間內獲取任務【keepAliveTime超時的使用】
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
// 不可以超時,則一直等待直到獲取到任務爲止【核心線程不退出的原因】
workQueue.take();
shutdown方法–溫柔的關閉方式
public void shutdown() {
// 獲取全局鎖--方法中有鎖重入
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 1. 權限校驗
checkShutdownAccess();
// 2. 校驗狀態。如果狀態大於等於SHUTDOWN,直接返回;否則設置線程池狀態爲SHUTDOWN
advanceRunState(SHUTDOWN);
// 3. 中斷空閒線程--getTask方法中正在等待任務的線程
interruptIdleWorkers();
onShutdown(); // ScheduledThreadPoolExecutor的hook方法
} finally {
mainLock.unlock();
}
// 嘗試將線程狀態轉變爲TERMINATED
tryTerminate();
}
執行一次順序關閉,執行以前提交的任務,並且拒絕新任務。
爲什麼可以中斷空閒線程?
任務隊列是Java併發工具中的阻塞隊列,其take和poll方法可以自動響應中斷。
java.util.concurrent.BlockingQueue.take()/put(E)
tryTerminate()
final void tryTerminate() {
for (;;) {
int c = ctl.get();
// 線程池狀態以及woker線程數校驗。不符合直接返回
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 設置池狀態爲TIDYING
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 調用線程池的hook方法
terminated();
} finally {
// 設置線程池狀態爲TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
// 通知因爲調用termination變量await方法而阻塞的所有線程。
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
關於termination變量,是mainLock的線程交互對象。Condition是將 Object 監視器方法(wait、notify 和 notifyAll)分解成截然不同的對象。
private final Condition termination = mainLock.newCondition();
isShutDown()方法–判斷是否執行過shutdown命令
public boolean isShutdown() {
return ! isRunning(ctl.get());
}
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
判斷線程池是否已關閉,狀態大於等於SHUTDOWN。
shutdownNow()方法–不溫柔的關閉方式
試圖停止所有正在執行的線程,暫停處理阻塞隊列中的任務,並返回一個等待執行任務的列表。不能保證終止線程,任務可能沒有響應中斷【不是強制中斷】。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
// 獲取鎖
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 1. 權限校驗
checkShutdownAccess();
// 2. 校驗狀態。如果狀態大於等於STOP,直接返回;否則設置線程池狀態爲STOP
advanceRunState(STOP);
// 3. 中斷所有worker線程
interruptWorkers();
// 4. 將隊列任務轉移到List中,同時刪除隊列中任務
tasks = drainQueue();
} finally {
// 解鎖
mainLock.unlock();
}
// 試圖終止線程池
tryTerminate();
return tasks;
}
內部方法調用
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 遍歷所有wokers集合,中斷所有正在執行任務的worker。
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
// Worker
void interruptIfStarted() {
Thread t;
// worker已經運行,並且線程不爲空,沒有被中斷
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
// 中斷線程
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
awaitTermination(long timeout, TimeUnit unit) :
當前線程阻塞,直到某個事件首先發生。三個事件分別是使用shutdown命令後所有任務執行完畢、超時結束或者線程被中斷。
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
// 獲取超時時間,納秒數
long nanos = unit.toNanos(timeout);
// 加鎖
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 死循環
for (;;) {
// 如果線程池是TERMINATED,返回true
if (runStateAtLeast(ctl.get(), TERMINATED))
return true;
// 超時時間爲0,返回false
if (nanos <= 0)
return false;
// 一直阻塞,直到超時時間結束、中斷、被通知(signalled)
nanos = termination.awaitNanos(nanos);
}
} finally {
// 解鎖
mainLock.unlock();
}
}
tryTerminate()方法在將線程池狀態設爲TERMINATED後,會調 termination.signalAll();通知喚醒termination變量上等待的線程。