概述
線程池解決兩個不同的問題:由於減少了每個任務的調用開銷,它們通常在執行大量異步任務時提供了更好的性能,並且提供了在執行任務集合時綁定和管理資源(包括線程)的方法。每個ThreadPoolExecutor還維護一些基本統計信息,比如已完成任務的數量。爲了在廣泛的上下文中起到作用,該類提供了許多可調參數和可擴展鉤子。ThreadPoolExecutor將根據corePoolSize和maximumPoolSize設置的邊界自動調整池大小。
核心線程、最大線程池的線程數量
當方法execute()中提交了一個新任務,並且運行的線程小於corePoolSize時,即使其他工作線程處於空閒狀態,也將創建一個新線程來處理請求。如果運行的線程大於corePoolSize但小於maximumPoolSize,則僅當隊列已滿時纔會創建一個新線程。通過將corePoolSize和maximumPoolSize設置爲相同的值,可以創建一個固定大小的線程池。通過將maximumPoolSize設置爲一個本質上無界的值,如Integer.MAX_VALUE,您允許池容納任意數量的併發任務。最典型的情況是,內核和最大池大小僅在構建時設置,但也可以使用setCorePoolSize和setMaximumPoolSize動態更改它們。
按需構建線程池
默認情況下,即使是核心線程也只是在新任務到達時才初始創建和啓動,但是可以使用#prestartCoreThread或#prestartAllCoreThreads方法動態地覆蓋這一點。如果使用非空隊列構造池,您可能希望預啓動線程。
創建新線程
使用ThreadFactory創建新線程。如果沒有另外指定,則使用executor #defaultThreadFactory,它創建的線程都位於相同的ThreadGroup中,並且具有相同的NORM_PRIORITY優先級和非守護進程狀態。通過提供一個不同的ThreadFactory,您可以更改線程的名稱、線程組、優先級、守護進程狀態。
如果ThreadFactory無法創建一個線程的時候,將會會返回null,執行程序會繼續,但可能無法執行任何任務。線程應該擁有修改線程的運行時權限。如果工作線程或正在使用線程池的其他線程不擁有此權限,服務可能會降級:配置更改可能不會及時生效,關閉池可能仍然處於可能終止但尚未完成的狀態。
保活時間
如果當前池中有超過corePoolSize的線程,如果空閒時間超過keepAliveTime,多餘的線程將被終止。這提供了一種在池沒有被積極使用時減少資源消耗的方法。如果池稍後變得更活躍,將構造新的線程。還可以使用方法#setKeepAliveTime(long,TimeUnit)動態更改此參數。使用以納秒爲單位的保活時間有效地使空閒線程的狀態從終止到關閉已達到不可用。默認情況下,keep-alive策略僅適用於擁有多於corePoolSize線程的情況。但是方法#allowCoreThreadTimeOut(boolean)也可以用於將超時策略應用於核心線程,只要keepAliveTime值是非零的。
任務隊列
任何BlockingQueue都可以用來傳輸和保存提交的任務。此隊列的使用與池大小進行交互:
- 如果運行的線程小於corePoolSize,則執行程序將會添加新線程來處理新來的任務而不是將任務放入任務隊裏中。
- 如果正在運行的線程多於corePoolSize的線程,那麼執行器總是傾向於對請求進行排隊,而不是添加新線程。
- 如果一個請求不能排隊,那麼將創建一個新線程來處理不能入隊的新任務。如果此時的線程的數量超過maximumPoolSize的話,那麼新來的任務將被拒絕。
三種常見的任務入隊的策略
1.直接傳遞
工作隊列的一個很好的默認選擇是SynchronousQueue,它將任務傳遞給那些沒有佔有這些任務的線程。在這裏,如果沒有立即可用的線程來運行任務,則對任務進行排隊的嘗試將失敗,因此將構造一個新線程。此策略在處理可能具有內部依賴項的請求集時可以避免鎖定。直接切換通常需要無界的最大池大小,以避免拒絕新提交的任務。相反的情況下,當命令到達的平均速度持續快於它們能夠被處理的速度時,可能會出現無限制的線程增長。
2.無界隊列
使用無界隊列(例如,LinkedBlockingQueue沒有預定義的容量)將導致在所有核新池大小的線程都處於繁忙狀態時,新任務在隊列中等待。因此,不會創建超過corePoolSize的線程。因此,maximumPoolSize的值沒有任何影響。當每個任務完全獨立於其他任務時,這可能是合適的,因此任務不會影響其他任務的執行;例如,在web頁面服務器中。而這種類型的排隊可以有效地消除請求的瞬間爆發,例如,在web頁面服務器中。儘管這種類型的排隊可以有效地消除短暫的請求爆發,但也要明白這一點,當命令的平均到達速度超過它們的處理速度時,工作隊列可能會無限制地增長。
3.有界隊列
當使用有限的maximumpoolsize時,有界隊列(例如ArrayBlockingQueue)有助於防止資源耗盡,但調優和控制可能更困難。隊列大小和最大池大小可以相互適配:使用大隊列和小池可以最小化CPU使用、OS資源和上下文切換開銷,但是可能會導致人爲的低吞吐量。如果任務經常阻塞(例如,如果它們是I/O綁定的),系統可能能夠爲比其他方法允許的更多的線程安排時間。使用小隊列通常需要更大的核心池大小,這會使cpu更忙,但可能會遇到不可接受的調度開銷,這也會降低吞吐量。
任務的拒絕
在方法execute(Runnable)在執行程序關閉時提交的新任務將被拒絕,在執行程序對最大線程和工作隊列容量都有有限的使用界限,也就是資源已經飽和時也將被拒絕。在這兩種情況下,execute方法調用其RejectedExecutionHandler的rejectedExecution(Runnable, ThreadPoolExecutor)方法。
提供了四個預定義的處理程序策略:
- 在默認的ThreadPoolExecutor.AbortPolicy中。處理程序在拒絕時拋出運行時RejectedExecutionException。
- ThreadPoolExecutor.CallerRunsPolicy。調用execute本身的線程運行任務。這提供了一個簡單的反饋控制機制,可以降低新任務提交的速度。
- ThreadPoolExecutor.DiscardPolicy。無法執行的任務被簡單地刪除。如果執行器沒有關閉,則刪除工作隊列頭部的任務,然後重試執行(可能再次失敗,導致重複執行)。
- ThreadPoolExecutor.DiscardPolicy。無法執行的任務被簡單地刪除。
可以定義和使用其他類型的RejectedExecutionHandler類。這樣做需要一些注意,特別是當策略設計爲僅在特定容量或隊列策略下工作時。
鉤子方法
這個類提供了受保護的可覆蓋的beforeExecute (Thread, Runnable)和afterExecute(Runnable, Throwable)方法,這些方法在執行每個任務之前和之後調用。這些可以用來操作執行環境;例如,重新初始化threadlocal、收集統計信息或添加日誌條目。此外,terminated()可以被重寫來執行那些一旦執行器已經完成的任何流程。
如果鉤子或回調方法拋出異常,內部工作線程可能會失敗並突然終止。
隊列的維護
方法getQueue()允許訪問工作隊列,用於監視和調試。強烈反對將此方法用於任何其他目的。提供的兩個方法remove(Runnable)和purge可用於在取消大量排隊任務時幫助存儲回收。
終止化
沒有程序中引用的且沒有剩餘線程的線程池將自動關閉。如果您想確保即使用戶忘記調用shutdown也要回收未引用的池的話,那麼必須通過設置適當的保持活動時間(使用0個核心線程的下限)和設置allowCoreThreadTimeOut(布爾值)來安排未使用的線程最終死亡。
源碼解析
先看看重要的全局變量
1.主池控制狀態ctl是一個原子整數,包含兩個概念字段:
workerCount,表示線程的有效數量
runState,指示是否運行、關閉等
2.爲了將它們包裝成一個整型來表示,我們將workerCount限制爲(229)-1(大約5億)個線程,而不是(231)-1(20 * 10億)個線程。如果將來出現這樣的問題,可以將變量更改爲AtomicLong,並調整下面的shift/mask常量。但是在需要之前,使用int可以使這段代碼更快更簡單。
3.workerCount是允許開始和不允許停止的工作線程的數量。該值可能與實際活動線程的數量有暫時的不同,例如當ThreadFactory在被請求時無法創建線程,以及在終止前已經退出的線程仍在執行記錄下的任務時。用戶可見的池大小被報告作爲當前工作線程集合的大小。
4.runState提供主要的生命週期控制,採用下面的值:
state | explain | value |
---|---|---|
RUNNING | 接受新任務並處理排隊的任務 | 1010 0000 0000 0000 0000 0000 0000 0000 |
SHUTDOWN | 不接受新任務,但是處理排隊的任務 | 0000 0000 0000 0000 0000 0000 0000 0000 |
STOP | 不接受新任務,不處理排隊的任務,並且中斷正在進行的任務 | 0010 0000 0000 0000 0000 0000 0000 0000 |
TIDYING | 所有任務都已終止,workerCount爲零,過渡到清理狀態的線程將運行terminate()方法 | 0100 0000 0000 0000 0000 0000 0000 0000 |
TERMINATED | terminated()方法已經終止 | 0110 0000 0000 0000 0000 0000 0000 0000 |
5.爲了允許有序比較,這些值之間的數值轉換順序很重要。運行狀態會隨着時間單調地增加,但不需要達到每個狀態,狀態的所有轉換情況如下:
transitions | explain |
---|---|
RUNNING -> SHUTDOWN | 調用shutdown(),可能在finalize()中隱式調用 |
(RUNNING or SHUTDOWN) -> STOP | 調用shutdownNow()時 |
SHUTDOWN -> TIDYING | 當隊列和池都爲空時 |
STOP -> TIDYING | 當池爲空時 |
TIDYING -> TERMINATED | 當terminated()方法完成時 |
當狀態達到終止時,在awaitterminate()中等待的線程將返回。
來看看構造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
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;
}
構造器參數的含義:
parameter | explain |
---|---|
corePoolSize | 線程池中存在的核心線程的數量,就算這些線程是空閒的也會保持這個數據。如果要取消這個性質,可以通過設置allowCoreThreadTimeOut() |
maximumPoolSize | 池中允許的最大線程數 |
keepAliveTime | 當線程數大於內核時,這是多餘的空閒線程在終止之前等待新任務的最長時間 |
unit | keepAliveTime參數的時間單位 |
workQueue | 保存任務的隊列,這個隊列只包含execute方法提交的Runnable類型的任務 |
threadFactory | 執行器創建新線程時使用的工廠,可以繼承threadFactory類,定製線程名稱等屬性的生成 |
handler | 由於達到線程邊界和隊列容量而阻塞執行時使用的處理程序,也可以設置自定義的新任務的拒絕策略 |
來看看核心方法:
在將來某個時候執行給定的任務。任務可以在新線程中執行,也可以在現有的池線程的線程中執行。如果無法提交任務供執行,或者因爲這個執行器已經關閉,或者因爲它的容量已經達到限制,那麼該任務將由當前RejectedExecutionHandler處理。
執行器的處理流程分爲三步:
- 如果線程池中正在運行的線程的數量小於corePoolSize,那麼創建一個新的線程並把新來的一個任務作爲線程的第一個任務來執行。對addWorker的調用原子性地檢查runState和workerCount,因此可以通過返回false來防止錯誤警報,因爲錯誤警報會在不應該添加線程的時候添加線程。
- 如果一個任務可以成功排隊,那麼我們仍然需要再次檢查是否應該添加一個線程(因爲自上次檢查以來已有的線程已經死亡),或者在進入這個方法後線程池關閉。因此,我們重新檢查狀態,如果必要的話,如果停止,則回滾隊列;如果沒有,則啓動一個新線程。
- 如果無法對任務排隊,則嘗試添加新線程。如果它失敗了,我們知道線程池已經被關閉或已經飽和,所以拒絕任務。
來看看全局變量
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; }
深層次的看看ctlOf的計算原理:
ctlOf實際上就是把runState和workCount的數據柔和在一次,同個ctl就能計算出這兩個值,也是很方便的
ctl = rs | wc;
假設現在線程池的狀態時Running,工作中線程的數量是2個:
1010 0000 0000 0000 0000 0000 0000 0000
|
0000 0000 0000 0000 0000 0000 0000 0010
1010 0000 0000 0000 0000 0000 0000 0010
在看看如何從ctl中獲取線程池的runState的?
runState = ctl & ~CAPACITY
1010 0000 0000 0000 0000 0000 0000 0010
&
1110 0000 0000 0000 0000 0000 0000 0000
1010 0000 0000 0000 0000 0000 0000 0000
可以看出結果=rs,再來驗證一下workerCount?
workerCount = ctl & CAPACITY
1010 0000 0000 0000 0000 0000 0000 0010 & 0001 1111 1111 1111 1111 1111 1111 1111 = 0000 0000 0000 0000 0000 0000 0000 0010
可以看出結果 = workerCount
總結一下:runState取的是ctl的前高三位,workerCount取得是ctl的低29位。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {// 第一步,如果工作的線程的數量小於核心線程數,通過addWorker方法創建新線程處理新來的任務
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {// 第二步,在次檢查狀態
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))// 第三步
reject(command);// 執行拒絕策略
}
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);// 重複性的原子檢查防止錯誤警報
// 檢查隊列是否爲空
// SHUTDOWN的狀態表示線程池不在接受新的任務
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))// workerCount添加成功後退出,之後進入創建新的新的線程來處理新任務
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)// 不相等有有的線程請求了
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);// 在後面去解析Worker.class
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;// 使用ReentrantLock的mainLock是因爲可以序列化那些被中斷的空閒的線程,特別在線程池在執行SHUTDOWNNOW的時候可以避免線程的干擾風暴。否則退出的線程會中斷那些還未中斷的線程。在線程池執行SHUTDOWN和SHUTDOWNNOW的時候使用mainLock確保workSet是穩定的,同時單獨檢查是否允許中斷和實際的中斷。
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
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;
}
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
final void reject(Runnable command) {
handler.rejectedExecution(command, this);// 執行拒絕策略,可以定製策略
}
再來看看Worker這個類是如何創建線程並執行第一任務的?
先看看構造函數?
Worker(Runnable firstTask) {
setState(-1); // 這裏設置成-1,在worker中只有兩個狀態,一個是-1,一個是0,-1是禁止當前的任務被幹擾
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);// 使用threadFactory命名,並將當前worker對象綁定到線程中,實際上保存的worker中run中的方法提供給線程start
}
在來看看worker內部的鎖機制是怎麼樣的?
先看看lock,業務邏輯在tryAcquire()
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {// 這裏表明只有當前鎖處於空閒的時候纔會加鎖成功
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
在看看unlock,其實要看tryRelease()
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
最後看worker最後執行的業務邏輯run()
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // 回頭看addWorker方法中將任務添加到workers集合成功之後開始調用worker線程的start方法啓動線程,因爲在worker初始化的時候已經將state設置成-1了,但是到了run方法的時候,線程池允許當前的worker線程被幹擾,所以現在通過unlock將worker的狀態歸爲0
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {// 走到這裏說明新線程已經創建好了,getTask就是從定義ThreadPoolExecutors時存儲任務的線程池的類型中獲取任務
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();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
/*
*從線城池定義的隊列中獲取任務
*/
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
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?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 如果是定時任務的話,則使用隊列的poll操作,可以實現超時處理並立刻返回結果(異常或者null,沒有超時的話則返回處理結果)
// 如果不是定時任務的話,則使用隊列的take操作,將當前線程阻塞,知道線程被喚醒獲取任務
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
ThreadPoolExecutor的處理總之要接合任務隊列的offer、poll、take操作
源碼解析完畢,歡迎大家評論溝通!!!!