基本概念
Thread t = new Thread();
t.start();
上面的代碼我們再熟悉不過了,因爲我們通常在需要開啓一個線程的時候都會這樣做。
但使用這樣的方式,有時候也會照成困擾。例如如果程序中存在大量的併發線程,這樣做會帶來什麼缺陷?
答案很明顯,會造成編寫工作繁雜,降低系統效率,線程難以管理等等問題。
在這種情況下,有沒有一種方式能夠讓我們避免這些困擾呢?有,也就是我們這裏研究的線程池(ThreadPoolExecutor)。
線程池的思想理解起來其實也很簡單,我們可以看做是我們創建了一個池,從此再需要開啓線程的時候:
我們就只負責向這個池中添加線程任務,而線程的執行,生命週期的管理等等工作,就都由池幫我們管理。
實際上,如果僅僅是想要使用ThreadPoolExecutor是很容易的。
因爲JAVA API裏的Executor類已經爲我們提供了四種工廠方法,獲取四種最常用的各具特點的線程池對象:
ExecutorService es = null;
// 1.
es = Executors.newCachedThreadPool();
// 2.
es = Executors.newFixedThreadPool(5);
// 3.
es = Executors.newSingleThreadExecutor();
// 4.
es = Executors.newSingleThreadScheduledExecutor();
我們來看API說明文檔裏對於ThreadPoolExecutor,有如下這樣一段說明:
爲了便於跨大量上下文使用,此類提供了很多可調整的參數和擴展鉤子 (hook)。但是,強烈建議程序員使用較爲方便的 Executors 工廠方法 Executors.newCachedThreadPool()(無界線程池,可以進行自動線程回收)、Executors.newFixedThreadPool(int)(固定大小線程池)和 Executors.newSingleThreadExecutor()(單個後臺線程),它們均爲大多數使用場景預定義了設置。
其實從以上的說明我們不難發現,雖然Executors類爲我們提供了足夠的工廠方法以獲取線程池對象。
但是通過方法命名及API說明,我們都不難發現,事實上Executors只是針對在不同場景的使用特點,對線程池的屬性進行了特定的設置,以簡化我們的使用。但其底部終究應該仍是離不開ThreadPoolExecutor。
通過源碼,我們能夠最好的驗證我們的猜想。那我們就不妨去看一看前面說到的工廠方法的源碼實現:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L,
TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService(
new ScheduledThreadPoolExecutor(1));
}
由此我們知道了,Executors提供的獲取線程池的工廠方法,底部正是通過構建ThreadPoolExecutor對象完成的。
ScheduledExecutorService例外,是通過構建ScheduledThreadPoolExecutor完成,但該類型繼承自ThreadPoolExecutor。
所以,如果我們不僅僅滿足於簡單的使用線程池,還想要了解其實現原理。那麼,研究ThreadPoolExecutor的源碼自然就是最好的方式。
構造器
按照我們的老套路,研究源碼自然先看看其構造器的定義,因爲使用對象首先需要構建對象。
而且我們前面說到的Executors提供的工廠方法也是通過設置不同的構造器參數,而返回不同使用特點的線程池對象。
// 核心池大小
private volatile int corePoolSize;
// 池最大大小
private volatile int maximumPoolSize;
// 任務隊列
private final BlockingQueue<Runnable> workQueue;
// 超過核心池大小的空閒線程的存活時間
private volatile long keepAliveTime;
// 線程工廠
private volatile ThreadFactory threadFactory;
// 拒絕任務時的處理策略
private volatile RejectedExecutionHandler handler;
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
/*
* 滿足以下任一條件,則報告非法參數異常:
* 1.corePoolSize(核心池大小)小於0
* 2.maximumPoolSize(池最大容量)小於或等於0
* 3.池最大容量小於核心池大小
* 4.keepAliveTime(空閒線程存活時間)小於0
*/
if (corePoolSize < 0 || maximumPoolSize <= 0
|| maximumPoolSize < corePoolSize || keepAliveTime < 0)
throw new IllegalArgumentException();
/*
* 滿足以下任一條件,則報告空指針異常:
* 1.workQueue(任務阻塞隊列)爲null
* 2.threadFactory(線程創建工廠)爲null
* 3.handler(拒絕任務處理策略對象)爲null
*/
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
// 賦值操作
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
對於ThreadPoolExecutor類來說,構造器的實現非常一目瞭然。我們從源碼中看到,雖然提供了四個類型的構造器,但最終都將回到第四種構造器的調用。
而我們需要關注的,自然就是這個構造器內,7個參數代表的含義分別是什麼了。這可以爲我們之後讀源碼打下一個基礎。
- corePoolSize:指線程池的核心大小。每當向線程池內提交新的任務,就會檢查當前池內運行的線程的數量。如果小於該值,就會創建新的線程;如果大於該值,則會將任務暫時安排進阻塞隊列。
- maximumPoolSize:指線程池的最大容量。設置該值的意義在於:當向池內添加新的任務,如果當前池內運行的線程數大於corePoolSize且小於maximumPoolSize;並且阻塞隊列已經排滿,則創建新的線程。
- keepAliveTime:指的是空閒線程的存活時間。但一定注意該存活時間只針對於那些超過corePoolSize的線程生效。也就是說,如果池內當前即使存在空閒的線程,但如果數量在corePoolSize範圍之內。該值則不會生效。
- unit:很簡單,就是設置keepAliveTime的時間單位(如:天、小時、分、秒等)
- workQueue:阻塞隊列,用來存儲等待執行的任務。一般來說,這裏的阻塞隊列有以下幾種選擇:1.ArrayBlockingQueue; 2.LinkedBlockingQueue; 3.SynchronousQueue;
- threadFactory:線程創建工廠,顧名思義,主要負責線程的創建工作。
- RejectedExecutionHandler:拒絕任務時的處理策略。拒絕任務是指:向池內新添加任務時,池內運行的線程數量已經大於或等於maximumPoolSize(即最大容量),那麼當前池則會拒絕此次添加的任務。
添加並執行任務
任何時候,當我們創建完對象,最關心的自然就是如何去使用該對象。而對象線程池來說,我們最關心的莫過於向池中添加任務並執行了。
這樣的操作實際上完成起來很簡單,就像我們下面這個例子中做的:
public class Demo {
static int i;
public static void main(String[] args) {
ExecutorService es = Executors.newCachedThreadPool();
es.execute(new Runnable() {
@Override
public void run() {
while (i < 10) {
System.out.println(++i);
}
}
});
}
}
就像我們在這個例子中看到的一樣,通常我們希望線程池爲我們執行任務。就是通過調用execute方法,並傳入Runnable對象完成的。
那麼,既然我們的目的是瞭解線程池的工作原理。毫無疑問的,研究該方法的源碼實現就是我們必不可少的一個重點:
public void execute(Runnable command) {
// 如果傳入任務爲null,報告空指針異常
if (command == null)
throw new NullPointerException();
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
} else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
}
}
我們來分析一下上面的代碼:
1.首先,判斷傳入的任務是否爲空。如果判斷結果爲真,則報告空指針異常。
2.接着進行的是一個“短路或”的邏輯判斷。首先判斷池內當前線程數(poolSize)是否大於核心池大小。
3.如果判斷結果爲真,則會造成短路,就可以直接結束當前判斷進入if代碼塊中繼續執行了。
4.否則則會繼續進行判斷,從而就會調用到addIfUnderCorePoolSize。顧名思義,就是小於核心池大小添加任務。
5.如果調用addIfUnderCorePoolSize的結果返回爲false,代表添加任務失敗。則同樣會進入if代碼塊中繼續執行了。
如果返回結果爲true,則代表添加任務成功,execute方法就可以執行結束了。
6.接下來就是當“短路或”的判斷運算通過,即當滿足以下兩個條件中的任一 一個時,就會繼續進行下一輪的判斷。
這兩個條件即是:“poolSize >= corePoolSize”和“addIfUnderCorePoolSize(command)返回false”。
7.這時,首先是判斷線程池狀態是否爲running狀態。那麼說到這裏,我們有必要了解一下,關於線程池的狀態的知識。
在ThreadPoolExecutor中定義了一個volatile變量,另外定義了幾個static final變量表示線程池的各個狀態:
volatile int runState;//當創建線程池後,初始時,線程池處於RUNNING狀態;
static final int RUNNING = 0;//如果調用了shutdown()方法,則線程池處於SHUTDOWN狀態,此時線程池不能夠接受新的任務,它會等待所有任務執行完畢;
static final int SHUTDOWN = 1;//如果調用了shutdownNow()方法,則線程池處於STOP狀態,此時線程池不能接受新的任務,並且會去嘗試終止正在執行的任務;
static final int STOP = 2;//當線程池處於SHUTDOWN或STOP狀態,並且所有工作線程已經銷燬,任務緩存隊列已經清空或執行結束後,線程池被設置爲TERMINATED狀態。
static final int TERMINATED = 3;
8.另一個運算條件是“workQueue.offer(command)”,這個運算的用意也很明顯,就是將任務加入阻塞隊列。
需要注意的就是這裏執行的是“短路與”運算,所以如果線程池的運行狀態只要不爲runnning,就不會執行加入阻塞隊列的操作了。
9.當線程池狀態爲runnning,並且將任務加入阻塞隊列成功,就會執行下一輪判斷“runState != RUNNING || poolSize == 0”。
否則就將進入else if判斷,執行到addIfUnderMaximumPoolSize方法,同樣的,這個方法的意義就是在小於池最大容量的時候添加任務。
10.接下來我們先分析“短路與”運算判斷通過的情況,我們說了,這時會繼續執行判斷“if (runState != RUNNING || poolSize == 0)”。思考一下,爲什麼我們已經將任務加入阻塞隊列了,還要執行這一次判斷呢?
其實從判斷的條件就不難想到,這是爲了如果出現在任務加入阻塞隊列的同時,其它線程調用了線程池的shutdown或者shutdownNow方法的情況,能夠得到處理。
如果判斷通過即代表出現了前面所說的“緊急情況”,執行的代碼是“ensureQueuedTaskHandled(command);”
就這個方法的命名,我們就可以知道,如果出現了這樣的情況,執行的方法就是爲了保證阻塞隊列的任務能夠被處理。
private void ensureQueuedTaskHandled(Runnable command) {
// 獲取鎖爲代碼加上同步
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
// 拒絕任務判斷標示
boolean reject = false;
Thread t = null;
try {
// 獲取線程池運行狀態
int state = runState;
// 如果運行狀態不爲RUNNING,並且將任務從阻塞隊列中移除
if (state != RUNNING && workQueue.remove(command))
// 設置拒絕標示
reject = true;
// 如果滿足運行狀態小於STOP;當前線程數小於核心大小;阻塞隊列不爲空
else if (state < STOP &&
poolSize < Math.max(corePoolSize, 1) &&
!workQueue.isEmpty())
// 添加一個runnable任務爲null的線程
t = addThread(null);
} finally {
mainLock.unlock();
}
// 判斷拒絕標示
if (reject)
// 拒絕任務
reject(command);
else if (t != null)
// 執行任務
t.start();
}
11.接着就是我們說的另一種情況,即else if的addIfUnderMaximumPoolSize判斷。
如果該方法的返回結果爲false,也就是說線程池已經到達了最大容量,添加任務失敗。那麼,就會調用reject方法,進行拒絕添加任務的策略處理、
void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
我想在execute方法當中,有兩個方法的源碼我們也很有興趣一看。就是“addIfUnderCorePoolSize”和“addIfUnderMaximumPoolSize”。
private boolean addIfUnderCorePoolSize(Runnable firstTask) {
Thread t = null;
// 同步
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 當前線程數小於核心池數目 並且 線程池運行狀態爲RUNNING
if (poolSize < corePoolSize && runState == RUNNING)
// 創建線程
t = addThread(firstTask);
} finally {
mainLock.unlock();
}
if (t == null)
return false;
t.start();
return true;
}
private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {
Thread t = null;
// 同步
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 當前線程數小於池最大容量 並且 線程池運行狀態爲RUNNING
if (poolSize < maximumPoolSize && runState == RUNNING)
t = addThread(firstTask);
} finally {
mainLock.unlock();
}
if (t == null)
return false;
t.start();
return true;
}
從源碼我們可以看到,這兩個方法的邏輯實際上很簡單。但我們注意到一個關鍵的方法調用“addThread”。
實際上我們在ensureQueuedTaskHandled中也已經看到過這個方法的調用,那我們肯定會關心該方法的實現:
private Thread addThread(Runnable firstTask) {
Worker w = new Worker(firstTask);
Thread t = threadFactory.newThread(w); //創建一個線程,執行任務
if (t != null) {
w.thread = t; //將創建的線程的引用賦值爲w的成員變量
workers.add(w);
int nt = ++poolSize; //當前線程數加1
if (nt > largestPoolSize)
largestPoolSize = nt; // 記錄線程池內出現過的線程的最大數量
}
return t;
}
該方法的實現邏輯同樣清晰,需要引起我們注意的自然是方法開頭就出現的一個類型:“Worker”。
Worker是定義在ThreadPoolExecutor當中的內部類,實際上是對我們添加進線程池的Runnable任務進行了封裝。
接下來,我們就來研究一下該內部類的源碼實現。
內部類Worker
private final class Worker implements Runnable {
// 同步鎖對象
private final ReentrantLock runLock = new ReentrantLock();
// 初始任務
private Runnable firstTask;
// 執行完成的任務數
volatile long completedTasks;
Thread thread;
Worker(Runnable firstTask) {
this.firstTask = firstTask;
}
// 任務是否處在活動狀態
boolean isActive() {
return runLock.isLocked();
}
// 封裝線程中斷操作
void interruptIfIdle() {
final ReentrantLock runLock = this.runLock;
if (runLock.tryLock()) {
try {
if (thread != Thread.currentThread())
thread.interrupt();
} finally {
runLock.unlock();
}
}
}
void interruptNow() {
thread.interrupt();
}
// 任務執行
private void runTask(Runnable task) {
final ReentrantLock runLock = this.runLock;
runLock.lock();
try {
if (runState < STOP && Thread.interrupted() && runState >= STOP)
thread.interrupt();
boolean ran = false;
/*
*beforeExecute方法是ThreadPoolExecutor類的一個方法,沒有具體實現,用戶可以根據
*自己需要重載這個方法和後面的afterExecute方法來進行一些統計信息,比如某個任務的執行時間等。
*/
beforeExecute(thread, task);
try {
task.run();
ran = true;
afterExecute(task, null);
// 增加完成執行的任務數量
++completedTasks;
} catch (RuntimeException ex) {
if (!ran)
afterExecute(task, ex);
throw ex;
}
} finally {
runLock.unlock();
}
}
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
// 循環執行任務
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}
}
我們看到Worker自身實現了Runnable接口,所以實際上我們在addThread方法中看到的以下代碼:
Worker w = new Worker(firstTask);
Thread t = threadFactory.newThread(w);
實際上其效果基本上就等同於:
Thread t = new Thread(w);
所以我們不難想到,對於Runnable接口的實現,最重點的自然就是對於run方法的覆寫。
而我們從Worker當中的run方法的實現可以看出,它首先執行的是通過構造器傳進來的任務firstTask。
在調用runTask()執行完firstTask之後,在while循環裏面不斷通過getTask()去取新的任務來執行。
那麼去哪裏取呢?自然是從任務緩存隊列“workQuene”裏面去取。接下來我們就來看一看這個方法的源碼實現:
Runnable getTask() {
// 等同於一個while循環
for (;;) {
try {
// 獲取運行狀態
int state = runState;
// 如果是STOP或者TERMINATED狀態
if (state > SHUTDOWN)
return null;
Runnable r;
// 如果是SHUTDOWN狀態
if (state == SHUTDOWN)
r = workQueue.poll(); // 從隊列中獲取任務
//如果線程數大於核心池大小或者允許爲核心池線程設置空閒時間,
else if (poolSize > corePoolSize || allowCoreThreadTimeOut)
//則通過poll取任務,若等待一定的時間取不到任務,則返回null
r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
else
//否則通過take取任務(其實也就是處於RUNNING狀態時)
r = workQueue.take();
if (r != null)
return r; // 如果取到的任務不爲null,則返回任務。
if (workerCanExit()) { // 如果取到的任務爲空,即r==null,則判斷當前的worker是否可以退出
if (runState >= SHUTDOWN) // Wake up others
interruptIdleWorkers(); // 中斷處於空閒狀態的線程
return null;
}
// Else retry
} catch (InterruptedException ie) {
// On interruption, re-check runState
}
}
}
以上的代碼我們已經做了較爲詳細的註釋,但額外再次強調的一段代碼是:
else if (poolSize > corePoolSize || allowCoreThreadTimeOut)
//則通過poll取任務,若等待一定的時間取不到任務,則返回null
r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
之所以再把這兩行代碼額外提出來解釋,是因爲這就是我們所說的線程超時的現象的原因所在。
這裏是做了一個“短路或”運算,首先判斷當前池內的線程數是否大於核心池數量。
如果判斷結果爲真,則直接進行後面的代碼。如果爲假,就要判斷allowCoreThreadTimeOut。
我們之前說到“keepAliveTime”被用於指定空閒線程的存活時間。但是該線程只針對於超過corePoolSize的線程有效。
實際上這正是因爲allowCoreThreadTimeOut默認爲false。在上面的代碼中如果allowCoreThreadTimeOut爲true,那麼也將執行之後的poll方法。
事實上,”workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)”與無參的poll方法相比,其不同的地方就在於:
該方法有一個等待時間,如果超過等待時間,仍然沒有獲取到隊列的頭元素,那麼就將返回null。
那麼也就是說,如果我們調用“allowsCoreThreadTimeOut()”方法設置該值爲true。那麼keepAliveTime就會對corePoolSize以內的線程也生效。
除此之外需要額外觀察的可能就是“workerCanExit”與“interruptIdleWorkers”。
我們首先來看“workerCanExit”的源碼實現:
private boolean workerCanExit() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
boolean canExit;
try {
canExit = runState >= STOP ||
workQueue.isEmpty() ||
(allowCoreThreadTimeOut &&
poolSize > Math.max(1, corePoolSize));
} finally {
mainLock.unlock();
}
return canExit;
}
這個方法通過一個boolean型的返回結果來判斷worker是否可以退出。那麼什麼時候該方法返回結果爲true呢?
從上面的代碼我們不難看到,需要滿足以下三種條件的任一一種:
- 1.runState >= STOP,即當前線程池已經處於STOP或者TERMINATED狀態。
- 2.workQueue.isEmpty(),也就是說阻塞任務隊列中的任務已經被執行完畢。
- 3.allowCoreThreadTimeOut &&poolSize > Math.max(1, corePoolSize)。如果設置了allowCoreThreadTimeOut爲true,並且池內線程數大於1.
OK,如果的確滿足我們上面所說的三種情況中的任一一種,那麼就會執行“interruptIdleWorkers”:
void interruptIdleWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfIdle();
} finally {
mainLock.unlock();
}
}
我們發現實際上喚醒中斷線程的操作,最終是通過定義在Worker類當中的interruptIfIdle方法完成的。它的源碼如下:
void interruptIfIdle() {
final ReentrantLock runLock = this.runLock;
// 獲取鎖
if (runLock.tryLock()) {
try {
// 如果work的成員thread不是系統的當前線程
if (thread != Thread.currentThread())
// 中斷線程狀態
thread.interrupt();
} finally {
runLock.unlock();
}
}
}
這個方法中值得注意的就是:我們通過調用tryLock()來獲取鎖。那麼如果當前worker正在執行任務。
這說明鎖當前並不是自由的,所以通過tryLock是無法獲取到鎖的。那麼對應的,如果成功獲取了鎖,說明當前worker處於空閒狀態。
線程池的工作原理
實際上到了,這裏我們就對整個線程池的工作原理有了一個瞭解了。我們在這裏再次進行一次回顧和總結,能夠加深我們的理解。
- 如果我們想要使用線程池,自然首先需要構建一個線程池對象。
我們可以根據自己的需要調用不同的構造器;也可以通過現有的Executors類中的工廠方法獲取。- 有了線程池對象後,我們可以通過調用execute方法,向池內添加需要執行的線程任務。
- 線程池接受到任務的過程中,如果當前池的運行狀態不爲RUNNING,那麼添加的任務將直接被拒絕。
如果池內當前存在的線程數小於核心池大小,則會創建新的線程並執行任務。反之則會將任務加入阻塞隊列。
如果阻塞隊列已經無法再添加新的任務,且當前線程數小於池的最大容量,則會創建新的額外線程去執行任務。
默認情況下,超過核心池數量的線程,空閒時間一旦超過keepAliveTime就會被中斷銷燬。- 接收到任務後,創建線程執行的工作是由addThread方法完成。該方法會將接受到的任務封裝成一個Worker對象。
- Worker實現了runnable接口,在其覆寫的run()方法當中,首先會執行firstTask,也就是初次接收到的任務。
- 當firstTask執行完畢,就會調用getTask方法循環的從阻塞隊列”workQuene”當中取出阻塞的任務去執行。從而也就實現了線程的複用,節約了消耗。
- 當從workQuene中獲取到的任務對象返回爲null,就代表阻塞隊列中的任務已經被取出執行完畢。
這時就會調用workerCanExit來判斷worker是否可以被退出了,如果判斷結果爲真:
緊接着就會通過調用interruptIdleWorkers()方法,將空閒的線程全部中斷,從而進行空閒線程的清理工作。- 在Worker的run方法中,循環執行完所有的task後。finally語句塊中執行了workerDone方法。
workerDone方法會將當前worker對象從Workers集合中移除,並且將poolSize執行自減運算。
一旦poolSize爲0了,就會執行tryTerminate()試圖關閉當前線程池。
由此,經過我們的總結整理,我們發現ThreadPoolExecutor的整個工作流程實際上算是十分清晰的。
以前在另一篇文章中看到過一個對於ThreadPoolExecutor工作原理的比喻,我覺得十分形象,大致意思就是:
假如有一個工廠,工廠裏面有10個工人,每個工人同時只能做一件任務。
那麼只要有工人是空閒的,來了任務就分配給空閒的工人做;
當10個工人都有任務在做時,如果還來了任務,就把任務進行排隊等待;
這個時候,如果有工人做完了當前手中的任務。那麼就從等待的任務清單中取出新的任務做。
但是如果說新任務數目增長的速度遠遠大於工人做任務的速度,那麼此時工廠主管可能會想補救措施,比如重新招2個臨時工人進來;
然後就將任務也分配給這2個臨時工人做;如果即使加上臨時工,工人做任務的速度還是不夠,此時工廠主管可能就要考慮不再接收新的任務或者拋棄前面的一些任務了。
當這12個工人當中有人空閒時,而新任務增長的速度又比較緩慢,工廠主管可能就考慮辭掉2個臨時工,只保持原來的10個工人了,畢竟請額外的工人是要花錢的。
到了這個時候,我們就對ThreadPoolExecutor有了很不錯的瞭解了。當然該類當中還有很多其他的方法,
但實際上這些方法的實現和使用有了我們以上的基礎,理解起來都很容易了,有興趣我們可以自己查看源碼和查閱API文檔來了解。
而值得一提的,還有以下的幾個東西,我們分別來看一下:
任務緩存隊列及排隊策略
在前面我們多次提到了任務緩存隊列,即workQueue,它用來存放等待執行的任務。
workQueue的類型爲BlockingQueue< Runnable >,通常可以取下面三種類型:
1)ArrayBlockingQueue:基於數組的先進先出隊列,此隊列創建時必須指定大小;
2)LinkedBlockingQueue:基於鏈表的先進先出隊列,如果創建時沒有指定此隊列大小,則默認爲Integer.MAX_VALUE;
3)synchronousQueue:這個隊列比較特殊,它不會保存提交的任務,而是將直接新建一個線程來執行新來的任務。
任務拒絕策略
任務拒絕策略就是指我們之前說到的,當線程池的任務緩存隊列已滿並且線程池中的線程數目達到maximumPoolSize,還有任務進入時的處理策略,通常有以下四種策略:
ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出常異常
RejectedExecutionException:也是丟棄任務,但是不拋出異常。
ThreadPoolExecutor.DiscardPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
線程池的關閉
ThreadPoolExecutor提供了兩個方法,用於線程池的關閉,分別是shutdown()和shutdownNow(),其中:
shutdown():不會立即終止線程池,而是要等所有任務緩存隊列中的任務都執行完後才終止,但再也不會接受新的任務
shutdownNow():立即終止線程池,並嘗試打斷正在執行的任務,並且清空任務緩存隊列,返回尚未執行的任務。