1 線程池的好處
線程使應用能夠更加充分合理地協調利用CPU、內存、網絡、I/O等系統資源.
線程的創建需要開闢虛擬機棧、本地方法棧、程序計數器等線程私有的內存空間;
在線程銷燬時需要回收這些系統資源.
頻繁地創建和銷燬線程會浪費大量的系統資源,增加併發編程風險.
在服務器負載過大的時候,如何讓新的線程等待或者友好地拒絕服務?
這些都是線程自身無法解決的;
所以需要通過線程池協調多個線程,並實現類似主次線程隔離、定時執行、週期執行等任務.
線程池的作用包括:
●利用線程池管理並複用線程、控制最大併發數等
●實現任務線程隊列緩存策略和拒絕機制
●實現某些與時間相關的功能
如定時執行、週期執行等
●隔離線程環境
比如,交易服務和搜索服務在同一臺服務器上,分別開啓兩個線程池,交易線程的資源消耗明顯要大;
因此,通過配置獨立的線程池,將較慢的交易服務與搜索服務隔離開,避免各服務線程相互影響.
在開發中,合理地使用線程池能夠帶來3個好處
- 降低資源消耗 通過重複利用已創建的線程,降低創建和銷燬線程造成的系統資源消耗
- 提高響應速度 當任務到達時,任務可以不需要等到線程創建就能立即執行
- 提高線程的可管理性 線程是稀缺資源,如果過多地創建,不僅會消耗系統資源,還會降低系統的穩定性,導致使用線程池可以進行統一分配、調優和監控。
在瞭解線程池的基本作用後,我們學習一下線程池是如何創建線程的
2 創建線程池
首先從ThreadPoolExecutor
構造方法講起,學習如何自定義ThreadFactory
和RejectedExecutionHandler
;
並編寫一個最簡單的線程池示例.
然後,通過分析ThreadPoolExecutor
的execute
和addWorker
兩個核心方法;
學習如何把任務線程加入到線程池中運行.
-
ThreadPoolExecutor 的構造方法如下
-
第1個參數: corePoolSize 表示常駐核心線程數
如果等於0,則任務執行完之後,沒有任何請求進入時銷燬線程池的線程;
如果大於0,即使本地任務執行完畢,核心線程也不會被銷燬.
這個值的設置非常關鍵;
設置過大會浪費資源;
設置過小會導致線程頻繁地創建或銷燬. -
第2個參數: maximumPoolSize 表示線程池能夠容納同時執行的最大線程數
從第1處來看,必須>=1.
如果待執行的線程數大於此值,需要藉助第5個參數的幫助,緩存在隊列中.
如果maximumPoolSize = corePoolSize
,即是固定大小線程池. -
第3個參數: keepAliveTime 表示線程池中的線程空閒時間
當空閒時間達到keepAliveTime
時,線程會被銷燬,直到只剩下corePoolSize
個線程;
避免浪費內存和句柄資源.
在默認情況下,當線程池的線程數大於corePoolSize
時,keepAliveTime
才起作用.
但是當ThreadPoolExecutor
的allowCoreThreadTimeOut = true
時,核心線程超時後也會被回收. -
第4個參數: TimeUnit表示時間單位
keepAliveTime的時間單位通常是TimeUnit.SECONDS. -
第5個參數: workQueue 表示緩存隊列
當請求的線程數大於maximumPoolSize
時,線程進入BlockingQueue
.
後續示例代碼中使用的LinkedBlockingQueue是單向鏈表,使用鎖來控制入隊和出隊的原子性;
兩個鎖分別控制元素的添加和獲取,是一個生產消費模型隊列. -
第6個參數: threadFactory 表示線程工廠
它用來生產一組相同任務的線程;
線程池的命名是通過給這個factory增加組名前綴來實現的.
在虛擬機棧分析時,就可以知道線程任務是由哪個線程工廠產生的. - 第7個參數: handler 表示執行拒絕策略的對象
當超過第5個參數workQueue
的任務緩存區上限的時候,就可以通過該策略處理請求,這是一種簡單的限流保護.
友好的拒絕策略可以是如下三種:
(1 ) 保存到數據庫進行削峯填谷;在空閒時再提取出來執行
(2)轉向某個提示頁面
(3)打印日誌
2.1.1 corePoolSize(核心線程數量)
線程池中應該保持的主要線程的數量.即使線程處於空閒狀態,除非設置了allowCoreThreadTimeOut
這個參數,當提交一個任務到線程池時,若線程數量<corePoolSize,線程池會創建一個新線程放入works(一個HashSet)中執行任務,即使其他空閒的基本線程能夠執行新任務也還是會創建新線程
等到需要執行的任務數大於線程池基本大小時就不再創建,會嘗試放入等待隊列workQueue
如果調用線程池的prestartAllCoreThreads()
,線程池會提前創建並啓動所有核心線程
2.1.2 maximumPoolSize(線程池最大線程數)
線程池允許創建的最大線程數
若隊列滿,並且已創建的線程數小於最大線程數,則線程池會再創建新的線程放入works中執行任務,CashedThreadPool的關鍵,固定線程數的線程池無效
若使用了×××任務隊列,這個參數就沒什麼效果
- workQueue
存儲待執行任務的阻塞隊列,這些任務必須是Runnable
的對象(如果是Callable對象,會在submit內部轉換爲Runnable對象)
-
runnableTaskQueue(任務隊列):用於保存等待執行的任務的阻塞隊列.可以選擇以下幾個阻塞隊列.
- LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按FIFO排序元素,吞吐量通常要高於ArrayBlockingQueue.靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列
-
SynchronousQueue:一個不存儲元素的阻塞隊列.每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於Linked-BlockingQueue,靜態工廠方法Executors.newCachedThreadPool使用了這個隊列
- ThreadFactory:用於設置創建線程的工廠,可以通過線程工廠給每個創建出來的線程設置更有意義的名字.使用開源框架guava提供ThreadFactoryBuilder可以快速給線程池裏的線程設置有意義的名字,代碼如下
new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build();
-
RejectedExecutionHandler(拒絕策略)
當隊列和線程池都滿,說明線程池飽和,必須採取一種策略處理提交的新任務
策略默認AbortPolicy
,表無法處理新任務時拋出異常
在JDK 1.5中Java線程池框架提供了以下4種策略- AbortPolicy:丟棄任務,拋出 RejectedExecutionException
- CallerRunsPolicy:只用調用者所在線程來運行任務,有反饋機制,使任務提交的速度變慢)。
- DiscardOldestPolicy
若沒有發生shutdown,嘗試丟棄隊列裏最近的一個任務,並執行當前任務, 丟棄任務緩存隊列中最老的任務,並且嘗試重新提交新的任務 -
DiscardPolicy:不處理,丟棄掉, 拒絕執行,不拋異常
當然,也可以根據應用場景需要來實現RejectedExecutionHandler接口自定義策略.如記錄日誌或持久化存儲不能處理的任務/** * Invokes the rejected execution handler for the given command. * Package-protected for use by ScheduledThreadPoolExecutor. */ final void reject(Runnable command) { // 執行拒絕策略 handler.rejectedExecution(command, this); }
handler
構造線程池時候就傳的參數,RejectedExecutionHandler
的實例RejectedExecutionHandler
在ThreadPoolExecutor
中有四個實現類可供我們直接使用,當然,也可以實現自己的策略,一般也沒必要。//只要線程池沒有被關閉,由提交任務的線程自己來執行這個任務 public static class CallerRunsPolicy implements RejectedExecutionHandler { public CallerRunsPolicy() { } /** * Executes task r in the caller's thread, unless the executor * has been shut down, in which case the task is discarded. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } }
// 不管怎樣,直接拋出 RejectedExecutionException 異常
// 默認的策略,如果我們構造線程池的時候不傳相應的 handler ,則指定使用這個
public static class AbortPolicy implements RejectedExecutionHandler {public AbortPolicy() { } /** * Always throws RejectedExecutionException. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task * @throws RejectedExecutionException always */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); }
}
// 不做任何處理,直接忽略掉這個任務
public static class DiscardPolicy implements RejectedExecutionHandler {
/**-
Creates a {@code DiscardPolicy}.
*/
public DiscardPolicy() { }/**
- Does nothing, which has the effect of discarding task r.
- @param r the runnable task requested to be executed
- @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
// 若線程池未被關閉
// 把隊列隊頭的任務(也就是等待了最長時間的)直接扔掉,然後提交這個任務到等待隊列中
public static class DiscardOldestPolicy implements RejectedExecutionHandler {public DiscardOldestPolicy() { } /** * Obtains and ignores the next task that the executor * would otherwise execute, if one is immediately available, * and then retries execution of task r, unless the executor * is shut down, in which case task r is instead discarded. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } }
}
- keepAliveTime(線程活動保持時間) 線程沒有任務執行時最多保持多久時間終止 線程池的工作線程空閒後,保持存活的時間。 所以,如果任務很多,並且每個任務執行的時間比較短,可以調大時間,提高線程的利用率
- TimeUnit(線程活動保持時間的單位):指示第三個參數的時間單位;可選的單位有天(DAYS)、小時(HOURS)、分鐘(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和納秒(NANOSECONDS,千分之一微秒)
從代碼第2處來看,隊列、線程工廠、拒絕處理服務都必須有實例對象;
但在實際編程中,很少有程序員對這三者進行實例化,而通過Executors
這個線程池靜態工廠提供默認實現;
那麼Exceutors與ThreadPoolExecutor是什麼關係呢?
Executors工廠類
ExecutorService
的抽象類AbstractExecutorService
提供了submit
、invokeAll
等方法的實現;
但是核心方法Executor.execute()
並沒有在這裏實現.
因爲所有的任務都在該方法執行,不同實現會帶來不同的執行策略.
通過Executors
的靜態工廠方法可以創建三個線程池的包裝對象
- ForkJoinPool、
- ThreadPoolExecutor
- ScheduledThreadPoolExecutor
● Executors.newWorkStealingPool
JDK8 引入,創建持有足夠線程的線程池支持給定的並行度;
並通過使用多個隊列減少競爭;
構造方法中把CPU數量設置爲默認的並行度.
返回ForkJoinPool
( JDK7引入)對象,它也是AbstractExecutorService
的子類
● Executors.newCachedThreadPoolmaximumPoolSize
最大可以至Integer.MAX_VALUE
,是高度可伸縮的線程池.
若達到該上限,相信沒有服務器能夠繼續工作,直接OOM.keepAliveTime
默認爲60秒;
工作線程處於空閒狀態,則回收工作線程;
如果任務數增加,再次創建出新線程處理任務.
● Executors.newScheduledThreadPool
線程數最大至Integer.MAX_ VALUE
,與上述相同,存在OOM風險.ScheduledExecutorService
接口的實現類,支持定時及週期性任務執行;
相比Timer
,ScheduledExecutorService
更安全,功能更強大.
與newCachedThreadPool
的區別是不回收工作線程.
● Executors.newSingleThreadExecutor
創建一個單線程的線程池,相當於單線程串行執行所有任務,保證按任務的提交順序依次執行.
● Executors.newFixedThreadPool
輸入的參數即是固定線程數;
既是核心線程數也是最大線程數;
不存在空閒線程,所以keepAliveTime
等於0.
其中使用了 LinkedBlockingQueue, 但是沒有設置上限!!!,堆積過多任務!!!
下面介紹LinkedBlockingQueue
的構造方法
使用這樣的×××隊列,如果瞬間請求非常大,會有OOM的風險;
除newWorkStealingPool
外,其他四個創建方式都存在資源耗盡的風險.
不推薦使用其中的任何創建線程池的方法,因爲都沒有任何限制,存在安全隱患.
Executors
中默認的線程工廠和拒絕策略過於簡單,通常對用戶不夠友好.
線程工廠需要做創建前的準備工作,對線程池創建的線程必須明確標識,就像藥品的生產批號一樣,爲線程本身指定有意義的名稱和相應的序列號.
拒絕策略應該考慮到業務場景,返回相應的提示或者友好地跳轉.
以下爲簡單的ThreadFactory 示例
上述示例包括線程工廠和任務執行體的定義;
通過newThread方法快速、統一地創建線程任務,強調線程一定要有特定意義的名稱,方便出錯時回溯.
- 單線程池:newSingleThreadExecutor()方法創建,五個參數分別是ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue())。含義是池中保持一個線程,最多也只有一個線程,也就是說這個線程池是順序執行任務的,多餘的任務就在隊列中排隊。
- 固定線程池:newFixedThreadPool(nThreads)方法創建
池中保持nThreads個線程,最多也只有nThreads個線程,多餘的任務也在隊列中排隊。
線程數固定且線程不超時
- 緩存線程池:newCachedThreadPool()創建,五個參數分別是ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue())。
含義是池中不保持固定數量的線程,隨需創建,最多可以創建Integer.MAX_VALUE個線程(說一句,這個數量已經大大超過目前任何操作系統允許的線程數了),空閒的線程最多保持60秒,多餘的任務在SynchronousQueue(所有阻塞、併發隊列在後續文章中具體介紹)中等待。
爲什麼單線程池和固定線程池使用的任務阻塞隊列是LinkedBlockingQueue(),而緩存線程池使用的是SynchronousQueue()呢?
因爲單線程池和固定線程池中,線程數量是有限的,因此提交的任務需要在LinkedBlockingQueue隊列中等待空餘的線程;而緩存線程池中,線程數量幾乎無限(上限爲Integer.MAX_VALUE),因此提交的任務只需要在SynchronousQueue隊列中同步移交給空餘線程即可。
- 單線程調度線程池:newSingleThreadScheduledExecutor()創建,五個參數分別是 (1, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue())。含義是池中保持1個線程,多餘的任務在DelayedWorkQueue中等待。
- 固定調度線程池:newScheduledThreadPool(n)創建,五個參數分別是 (n, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue())。含義是池中保持n個線程,多餘的任務在DelayedWorkQueue中等待。
有一項技術可以緩解執行時間較長任務造成的影響,即限定任務等待資源的時間,而不要無限的等待
先看第一個例子,測試單線程池、固定線程池和緩存線程池(注意增加和取消註釋):
public class ThreadPoolExam {
public static void main(String[] args) {
//first test for singleThreadPool
ExecutorService pool = Executors.newSingleThreadExecutor();
//second test for fixedThreadPool
// ExecutorService pool = Executors.newFixedThreadPool(2);
//third test for cachedThreadPool
// ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
pool.execute(new TaskInPool(i));
}
pool.shutdown();
}
}
class TaskInPool implements Runnable {
private final int id;
TaskInPool(int id) {
this.id = id;
}
@Override
public void run() {
try {
for (int i = 0; i < 5; i++) {
System.out.println("TaskInPool-["+id+"] is running phase-"+i);
TimeUnit.SECONDS.sleep(1);
}
System.out.println("TaskInPool-["+id+"] is over");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
如圖爲排查底層公共緩存調用出錯時的截圖
綠色框採用自定義的線程工廠,明顯比藍色框默認的線程工廠創建的線程名稱擁有更多的額外信息:如調用來源、線程的業務含義,有助於快速定位到死鎖、StackOverflowError 等問題.
拒絕策略
下面再簡單地實現一下RejectedExecutionHandler
;
實現了接口的rejectedExecution
方法,打印出當前線程池狀態
在ThreadPoolExecutor
中提供了四個公開的內部靜態類
● AbortPolicy - 默認
丟棄任務並拋出RejectedExecutionException
● DiscardPolicy - 不推薦
丟棄任務,但不拋異常.
● DiscardOldestPolicy
拋棄隊列中等待最久的任務,然後把當前任務加入隊列中.
● CallerRunsPolicy
調用任務的run()方法繞過線程池直接執行.
根據之前實現的線程工廠和拒絕策略,線程池的相關代碼實現如下
當任務被拒絕的時候,拒絕策略會打印出當前線程池的大小已經達到了maximumPoolSize=2
,且隊列已滿,完成的任務數提示已經有1個(最後一行).
源碼講解
在ThreadPoolExecutor
的屬性定義中頻繁地用位運算來表示線程池狀態;
位運算是改變當前值的一種高效手段.
下面從屬性定義開始
Integer 有32位;
最右邊29位表工作線程數;
最左邊3位表示線程池狀態,可表示從0至7的8個不同數值
線程池的狀態用高3位表示,其中包括了符號位.
五種狀態的十進制值按從小到大依次排序爲
RUNNING < SHUTDOWN < STOP < TIDYING <TERMINATED
這樣設計的好處是可以通過比較值的大小來確定線程池的狀態.
例如程序中經常會出現isRunning的判斷:
- 000-1111111111111111111111111;
類似於子網掩碼,用於與運算;
得到左邊3位,還是右邊29位
用左邊3位,實現5種線程池狀態;
在左3位之後加入中畫線有助於理解;
-
111 - 0000000000000000000000000000(十進制: -536, 870, 912);
該狀態表 線程池能接受新任務 -
000 - 0000000000000000000000000(十進制: 0);
此狀態不再接受新任務,但可繼續執行隊列中的任務 -
001 - 00000000000000000000000000(十進制: 536,870, 912);
此狀態全面拒絕,並中斷正在處理的任務 -
010 - 00000000000000000000000000.(十進制值: 1, 073, 741, 824);
該狀態表 所有任務已經被終止 - 101 - 000000000000000000000000000(十進制值: 1, 610,612, 736)
該狀態表 已清理完現場
與運算,比如 001 - 000000000000000000000100011 表 67個工作線程;
掩碼取反: 111 - 00000000000000000000000.,即得到左邊3位001;
表示線程池當前處於STOP狀態
同理掩碼 000 - 11111111111111111111,得到右邊29位,即工作線程數
把左3位與右29位或運算,合併成一個值
我們都知道Executor
接口有且只有一個方法execute()
;
通過參數傳入待執行線程的對象.
下面分析ThreadPoolExecutor
關於execute()
方法的實現
線程池執行任務的方法如下
/**
* Executes the given task sometime in the future. The task
* may execute in a new thread or in an existing pooled thread.
*
* If the task cannot be submitted for execution, either because this
* executor has been shutdown or because its capacity has been reached,
* the task is handled by the current {@code RejectedExecutionHandler}.
*
* @param command the task to execute
* @throws RejectedExecutionException at discretion of
* {@code RejectedExecutionHandler}, if the task
* cannot be accepted for execution
* @throws NullPointerException if {@code command} is null
*/
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
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* 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
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
// 返回包含線程數及線程池狀態的Integer 類型數值
int c = ctl.get();
// 若工作線程數 < 核心線程數,則創建線程並執行當前任務
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
execute
方法在不同的階段有三次addWorker
的嘗試動作。
return;
// 若創建失敗,爲防止外部已經在線程池中加入新任務,在此重新獲取一下
c = ctl.get();
}
// 若 工作線程數 >=核心線程數 或線程創建失敗,則將當前任務放到工作隊列中
// 只有線程池處於 RUNNING 態,才執行後半句 : 置入隊列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 只有線程池處於 RUNNING 態,才執行後半句 : 置入隊列
if (! isRunning(recheck) && remove(command))
reject(command);
// 若之前的線程已被消費完,新建一個線程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
// 核心線程和隊列都已滿,嘗試創建一個新線程
}
else if (!addWorker(command, false))
// 拋出RejectedExecutionException異常
// 若 addWorker 返回是 false,即創建失敗,則喚醒拒絕策略.
reject(command);
}
發生拒絕的理由有兩個
( 1 )線程池狀態爲非RUNNING狀態
(2)等待隊列已滿。
下面繼續分析addWorker
addWorker 源碼解析
根據當前線程池狀態,檢查是否可以添加新的任務線程,若可以則創建並啓動任務;
若一切正常則返回true;
返回false的可能性如下
- 線程池沒有處於
RUNNING
態 - 線程工廠創建新的任務線程失敗
參數
- firstTask
外部啓動線程池時需要構造的第一個線程,它是線程的母體 - core
新增工作線程時的判斷指標- true
需要判斷當前RUNNING
態的線程是否少於corePoolsize
- true
- false
需要判斷當前RUNNING
態的線程是否少於maximumPoolsize
- firstTask
這段代碼晦澀難懂,部分地方甚至違反代碼規約,但其中蘊含豐富的編碼知識點
-
第1處,配合循環語句出現的label,類似於goto 作用
label 定義時,必須把標籤和冒號的組合語句緊緊相鄰定義在循環體之前,否則會編譯出錯.
目的是 在實現多重循環時能夠快速退出到任何一層;
出發點似乎非常貼心,但在大型軟件項目中,濫用標籤行跳轉的後果將是災難性的.
示例代碼中在retry
下方有兩個無限循環;
在workerCount
加1成功後,直接退出兩層循環. -
第2處,這樣的表達式不利於閱讀,應如是
-
第3處,與第1處的標籤呼應,
AtomicInteger
對象的加1操作是原子性的;break retry
表 直接跳出與retry
相鄰的這個循環體 -
第4處,此
continue
跳轉至標籤處,繼續執行循環.
如果條件爲false,則說明線程池還處於運行狀態,即繼續在for(;)
循環內執行. -
第5處,
compareAndIncrementWorkerCount
方法執行失敗的概率非常低.
即使失敗,再次執行時成功的概率也是極高的,類似於自旋原理.
這裏是先加1,創建失敗再減1,這是輕量處理併發創建線程的方式;
如果先創建線程,成功再加1,當發現超出限制後再銷燬線程,那麼這樣的處理方式明顯比前者代價要大. - 第6處,
Worker
對象是工作線程的核心類實現,部分源碼如下
它實現了Runnable
接口,並把本對象作爲參數輸入給run()
中的runWorker (this)
;
所以內部屬性線程thread
在start
的時候,即會調用runWorker
.
總結
線程池的相關源碼比較精煉,還包括線程池的銷燬、任務提取和消費等,與線程狀態圖一樣,線程池也有自己獨立的狀態轉化流程,本節不再展開。
總結一下,使用線程池要注意如下幾點:
(1)合理設置各類參數,應根據實際業務場景來設置合理的工作線程數。
(2)線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。
(3)創建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。
線程池不允許使用Executors,而是通過ThreadPoolExecutor的方式創建,這樣的處理方式能更加明確線程池的運行規則,規避資源耗盡的風險。
進一步查看源碼發現,這些方法最終都調用了ThreadPoolExecutor和ScheduledThreadPoolExecutor的構造函數
而ScheduledThreadPoolExecutor繼承自ThreadPoolExecutor
0.2 ThreadPoolExecutor 自定義線程池
它們都是某種線程池,可以控制線程創建,釋放,並通過某種策略嘗試複用線程去執行任務的一個管理框架
,因此最終所有線程池的構造函數都調用了Java5後推出的ThreadPoolExecutor的如下構造函數
Java默認提供的線程池
Java中的線程池是運用場景最多的併發框架,幾乎所有需要異步或併發執行任務的程序都可以使用線程池
我們只需要將待執行的方法放入 run 方法中,將 Runnable 接口的實現類交給線程池的
execute 方法,作爲他的一個參數,比如:
Executor e=Executors.newSingleThreadExecutor();
e.execute(new Runnable(){ //匿名內部類 public void run(){
//需要執行的任務
}
});
線程池的實現原理
ThreadPoolExecutor執行execute()分4種情況
- 若當前運行的線程少於
corePoolSize
,則創建新線程來執行任務(執行這一步需要獲取全局鎖) - 若運行的線程多於或等於
corePoolSize
,則將任務加入BlockingQueue
- 若無法將任務加入
BlockingQueue
,則創建新的線程來處理任務(執行這一步需要獲取全局鎖) - 若創建新線程將使當前運行的線程超出
maximumPoolSize
,任務將被拒絕,並調用RejectedExecutionHandler.rejectedExecution()
採取上述思路,是爲了在執行execute()
時,儘可能避免獲取全局鎖
在ThreadPoolExecutor完成預熱之後(當前運行的線程數大於等於corePoolSize),幾乎所有的execute()方法調用都是執行步驟2,而步驟2不需要獲取全局鎖
#源碼分析
/**
* 檢查是否可以根據當前池狀態和給定的邊界(核心或最大)
* 添加新工作線程。如果是這樣,工作線程數量會相應調整,如果可能的話,一個新的工作線程創建並啓動
* 將firstTask作爲其運行的第一項任務。
* 如果池已停止此方法返回false
* 如果線程工廠在被訪問時未能創建線程,也返回false
* 如果線程創建失敗,或者是由於線程工廠返回null,或者由於異常(通常是在調用Thread.start()後的OOM)),我們乾淨地回滾。
*
* @param core if true use corePoolSize as bound, else
* maximumPoolSize. (A boolean indicator is used here rather than a
* value to ensure reads of fresh values after checking other pool
* state).
* @return true if successful
*/
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
/**
* Check if queue empty only if necessary.
*
* 如果線程池已關閉,並滿足以下條件之一,那麼不創建新的 worker:
* 1. 線程池狀態大於 SHUTDOWN,也就是 STOP, TIDYING, 或 TERMINATED
* 2. firstTask != null
* 3. workQueue.isEmpty()
* 簡單分析下:
* 狀態控制的問題,當線程池處於 SHUTDOWN ,不允許提交任務,但是已有任務繼續執行
* 當狀態大於 SHUTDOWN ,不允許提交任務,且中斷正在執行任務
* 多說一句:若線程池處於 SHUTDOWN,但 firstTask 爲 null,且 workQueue 非空,是允許創建 worker 的
*
*/
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))
break retry;
// 由於有併發,重新再讀取一下 ctl
c = ctl.get(); // Re-read ctl
// 正常如果是 CAS 失敗的話,進到下一個裏層的for循環就可以了
// 可如果是因爲其他線程的操作,導致線程池的狀態發生了變更,如有其他線程關閉了這個線程池
// 那麼需要回到外層的for循環
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
/* *
* 到這裏,我們認爲在當前這個時刻,可以開始創建線程來執行任務
*/
// worker 是否已經啓動
boolean workerStarted = false;
// 是否已將這個 worker 添加到 workers 這個 HashSet 中
boolean workerAdded = false;
Worker w = null;
try {
// 把 firstTask 傳給 worker 的構造方法
w = new Worker(firstTask);
// 取 worker 中的線程對象,Worker的構造方法會調用 ThreadFactory 來創建一個新的線程
final Thread t = w.thread;
if (t != null) {
//先加鎖
final ReentrantLock mainLock = this.mainLock;
// 這個是整個類的全局鎖,持有這個鎖才能讓下面的操作“順理成章”,
// 因爲關閉一個線程池需要這個鎖,至少我持有鎖的期間,線程池不會被關閉
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
// 小於 SHUTTDOWN 即 RUNNING
// 如果等於 SHUTDOWN,不接受新的任務,但是會繼續執行等待隊列中的任務
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
// worker 裏面的 thread 不能是已啓動的
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 加到 workers 這個 HashSet 中
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 若添加成功
if (workerAdded) {
// 啓動線程
t.start();
workerStarted = true;
}
}
} finally {
// 若線程沒有啓動,做一些清理工作,若前面 workCount 加了 1,將其減掉
if (! workerStarted)
addWorkerFailed(w);
}
// 返回線程是否啓動成功
return workerStarted;
}
看下 addWorkFailed
worker
中的線程 start
後,其 run
方法會調用 runWorker
繼續往下看 runWorker
// worker 線程啓動後調用,while 循環(即自旋!)不斷從等待隊列獲取任務並執行
// worker 初始化時,可指定 firstTask,那麼第一個任務也就可以不需要從隊列中獲取
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// 該線程的第一個任務(若有)
Runnable task = w.firstTask;
w.firstTask = null;
// 允許中斷
w.unlock();
boolean completedAbruptly = true;
try {
// 循環調用 getTask 獲取任務
while (task != null || (task = getTask()) != null) {
w.lock();
// 若線程池狀態大於等於 STOP,那麼意味着該線程也要中斷
/**
* 若線程池STOP,請確保線程 已被中斷
* 如果沒有,請確保線程未被中斷
* 這需要在第二種情況下進行重新檢查,以便在關中斷時處理shutdownNow競爭
*/
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) {
// 這裏不允許拋出 Throwable,所以轉換爲 Error
thrown = x; throw new Error(x);
} finally {
// 也是一個鉤子方法,將 task 和異常作爲參數,留給需要的子類實現
afterExecute(task, thrown);
}
} finally {
// 置空 task,準備 getTask 下一個任務
task = null;
// 累加完成的任務數
w.completedTasks++;
// 釋放掉 worker 的獨佔鎖
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 到這裏,需要執行線程關閉
// 1. 說明 getTask 返回 null,也就是說,這個 worker 的使命結束了,執行關閉
// 2. 任務執行過程中發生了異常
// 第一種情況,已經在代碼處理了將 workCount 減 1,這個在 getTask 方法分析中說
// 第二種情況,workCount 沒有進行處理,所以需要在 processWorkerExit 中處理
processWorkerExit(w, completedAbruptly);
}
}
看看 getTask()
// 此方法有三種可能
// 1. 阻塞直到獲取到任務返回。默認 corePoolSize 之內的線程是不會被回收的,它們會一直等待任務
// 2. 超時退出。keepAliveTime 起作用的時候,也就是如果這麼多時間內都沒有任務,那麼應該執行關閉
// 3. 如果發生了以下條件,須返回 null
// 池中有大於 maximumPoolSize 個 workers 存在(通過調用 setMaximumPoolSize 進行設置)
// 線程池處於 SHUTDOWN,而且 workQueue 是空的,前面說了,這種不再接受新的任務
// 線程池處於 STOP,不僅不接受新的線程,連 workQueue 中的線程也不再執行
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
// 允許核心線程數內的線程回收,或當前線程數超過了核心線程數,那麼有可能發生超時關閉
// 這裏 break,是爲了不往下執行後一個 if (compareAndDecrementWorkerCount(c))
// 兩個 if 一起看:如果當前線程數 wc > maximumPoolSize,或者超時,都返回 null
// 那這裏的問題來了,wc > maximumPoolSize 的情況,爲什麼要返回 null?
// 換句話說,返回 null 意味着關閉線程。
// 那是因爲有可能開發者調用了 setMaximumPoolSize 將線程池的 maximumPoolSize 調小了
// 如果此 worker 發生了中斷,採取的方案是重試
// 解釋下爲什麼會發生中斷,這個讀者要去看 setMaximumPoolSize 方法,
// 如果開發者將 maximumPoolSize 調小了,導致其小於當前的 workers 數量,
// 那麼意味着超出的部分線程要被關閉。重新進入 for 循環,自然會有部分線程會返回 null
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
// CAS 操作,減少工作線程數
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 {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
// 如果此 worker 發生了中斷,採取的方案是重試
// 解釋下爲什麼會發生中斷,這個讀者要去看 setMaximumPoolSize 方法,
// 如果開發者將 maximumPoolSize 調小了,導致其小於當前的 workers 數量,
// 那麼意味着超出的部分線程要被關閉。重新進入 for 循環,自然會有部分線程會返回 null
timedOut = false;
}
}
}
到這裏,基本上也說完了整個流程,回到 execute(Runnable command) 方法,看看各個分支,我把代碼貼過來一下:
/**
* Executes the given task sometime in the future. The task
* may execute in a new thread or in an existing pooled thread.
*
* If the task cannot be submitted for execution, either because this
* executor has been shutdown or because its capacity has been reached,
* the task is handled by the current {@code RejectedExecutionHandler}.
*
* @param command the task to execute
* @throws RejectedExecutionException at discretion of
* {@code RejectedExecutionHandler}, if the task
* cannot be accepted for execution
* @throws NullPointerException if {@code command} is null
*/
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
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* 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
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
//表示 “線程池狀態” 和 “線程數” 的整數
int c = ctl.get();
// 如果當前線程數少於核心線程數,直接添加一個 worker 執行任務,
// 創建一個新的線程,並把當前任務 command 作爲這個線程的第一個任務(firstTask)
if (workerCountOf(c) < corePoolSize) {
// 添加任務成功,即結束
// 執行的結果,會包裝到 FutureTask
// 返回 false 代表線程池不允許提交任務
if (addWorker(command, true))
return;
c = ctl.get();
}
// 到這說明,要麼當前線程數大於等於核心線程數,要麼剛剛 addWorker 失敗
// 如果線程池處於 RUNNING ,把這個任務添加到任務隊列 workQueue 中
if (isRunning(c) && workQueue.offer(command)) {
/* 若任務進入 workQueue,我們是否需要開啓新的線程
* 線程數在 [0, corePoolSize) 是無條件開啓新線程的
* 若線程數已經大於等於 corePoolSize,則將任務添加到隊列中,然後進到這裏
*/
int recheck = ctl.get();
// 若線程池不處於 RUNNING ,則移除已經入隊的這個任務,並且執行拒絕策略
if (! isRunning(recheck) && remove(command))
reject(command);
// 若線程池還是 RUNNING ,且線程數爲 0,則開啓新的線程
// 這塊代碼的真正意圖:擔心任務提交到隊列中了,但是線程都關閉了
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 若 workQueue 滿,到該分支
// 以 maximumPoolSize 爲界創建新 worker,
// 若失敗,說明當前線程數已經達到 maximumPoolSize,執行拒絕策略
else if (!addWorker(command, false))
reject(command);
}
工作線程:線程池創建線程時,會將線程封裝成工作線程Worker,Worker在執行完任務後,還會循環獲取工作隊列裏的任務來執行.我們可以從Worker類的run()方法裏看到這點
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
//先加鎖
final ReentrantLock mainLock = this.mainLock;
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;
}
線程池中的線程執行任務分兩種情況
- 在execute()方法中創建一個線程時,會讓這個線程執行當前任務
- 這個線程執行完上圖中 1 的任務後,會反覆從BlockingQueue獲取任務來執行
線程池的使用
2.2 向線程池提交任務
可以使用兩個方法向線程池提交任務
2.2.1 execute()
用於提交不需要返回值的任務,所以無法判斷任務是否被線程池執行成功.通過以下代碼可知execute()方法輸入的任務是一個Runnable類的實例.
threadsPool.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
});
從運行結果可以看出,單線程池中的線程是順序執行的。固定線程池(參數爲2)中,永遠最多隻有兩個線程併發執行。緩存線程池中,所有線程都併發執行。
第二個例子,測試單線程調度線程池和固定調度線程池。
public class ScheduledThreadPoolExam {
public static void main(String[] args) {
//first test for singleThreadScheduledPool
ScheduledExecutorService scheduledPool = Executors.newSingleThreadScheduledExecutor();
//second test for scheduledThreadPool
// ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2);
for (int i = 0; i < 5; i++) {
scheduledPool.schedule(new TaskInScheduledPool(i), 0, TimeUnit.SECONDS);
}
scheduledPool.shutdown();
}
}
class TaskInScheduledPool implements Runnable {
private final int id;
TaskInScheduledPool(int id) {
this.id = id;
}
@Override
public void run() {
try {
for (int i = 0; i < 5; i++) {
System.out.println("TaskInScheduledPool-["+id+"] is running phase-"+i);
TimeUnit.SECONDS.sleep(1);
}
System.out.println("TaskInScheduledPool-["+id+"] is over");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
從運行結果可以看出,單線程調度線程池和單線程池類似,而固定調度線程池和固定線程池類似。
總結:
- 如果沒有特殊要求,使用緩存線程池總是合適的;
- 如果只能運行一個線程,就使用單線程池。
- 如果要運行調度任務,則按需使用調度線程池或單線程調度線程池
- 如果有其他特殊要求,則可以直接使用ThreadPoolExecutor類的構造函數來創建線程池,並自己給定那五個參數。
2.2.2 submit()
用於提交需要返回值的任務.線程池會返回一個future類型對象,通過此對象可以判斷任務是否執行成功
並可通過get()獲取返回值,get()會阻塞當前線程直到任務完成,而使用get(long timeout,TimeUnit unit)方法則會阻塞當前線程一段時間後立即返回,這時候可能任務沒有執行完.
Future<Object> future = executor.submit(harReturnValuetask);
try {
Object s = future.get();
} catch (InterruptedException e) {
// 處理中斷異常
} catch (ExecutionException e) {
// 處理無法執行任務異常
} finally {
// 關閉線程池
executor.shutdown();
}
2.3 關閉線程池
可通過調用線程池的shutdown或shutdownNow方法來關閉線程池.
它們的原理是遍歷線程池中的工作線程,然後逐個調用線程的interrupt方法來中斷線程,所以無法響應中斷的任務可能永遠無法終止.
但是它們存在一定的區別
- shutdownNow首先將線程池的狀態設置成STOP,然後嘗試停止所有的正在執行或暫停任務的線程,並返回等待執行任務的列表
- shutdown只是將線程池的狀態設置成SHUTDOWN狀態,然後中斷所有沒有正在執行任務的線程.
只要調用了這兩個關閉方法中的任意一個,isShutdown方法就會返回true.
當所有的任務都已關閉後,才表示線程池關閉成功,這時調用isTerminaed方法會返回true.
至於應該調用哪一種方法,應該由提交到線程池的任務的特性決定,通常調用shutdown方法來關閉線程池,若任務不一定要執行完,則可以調用shutdownNow方法.
##2.4 合理配置
要想合理地配置線程池,就必須首先分析任務特性,可從以下幾個角度來分析
- 任務的性質:CPU密集型任務、IO密集型任務和混合型任務
- 任務的優先級:高、中和低
- 任務的執行時間:長、中和短
- 任務的依賴性:是否依賴其他系統資源,如數據庫連接。
性質不同的任務可以用不同規模的線程池分開處理
- CPU密集型任務
應配置儘可能小的線程,配置N(CPU)+1
或者N(CPU) * 2
- I/O密集型任務
業務讀取較多,線程並不是一直在執行任務,則應配置儘可能多的線程N(CPU)/1 - 阻塞係數(0.8~0.9)
- 混合型的任務,如果可以拆分,將其拆分成一個CPU密集型任務和一個IO密集型任務,只要這兩個任務執行的時間相差不是太大,那麼分解後執行的吞吐量將高於串行執行的吞吐量.如果這兩個任務執行時間相差太大,則沒必要進行分解.
可以通過Runtime.getRuntime().availableProcessors()方法獲得當前設備的CPU個數.
優先級不同的任務可以使用PriorityBlockingQueue處理.它可以讓優先級高
的任務先執行.
注意 如果一直有優先級高的任務提交到隊列裏,那麼優先級低的任務可能永遠不能執行
執行時間不同的任務可以交給不同規模的線程池來處理,或者可以使用優先級隊列,讓執行時間短的任務先執行.
依賴數據庫連接池的任務,因爲線程提交SQL後需要等待數據庫返回結果,等待的時間越長,則CPU空閒時間就越長,那麼線程數應該設置得越大,這樣才能更好地利用CPU.
建議使用有界隊列 有界隊列能增加系統的穩定性和預警能力,可以根據需要設大一點,比如幾千.
假如系統裏後臺任務線程池的隊列和線程池全滿了,不斷拋出拋棄任務的異常,通過排查發現是數據庫出現了問題,導致執行SQL變得非常緩慢,因爲後臺任務線程池裏的任務全是需要向數據庫查詢和插入數據的,所以導致線程池裏的工作線程全部阻塞,任務積壓在線程池裏.
如果我們設置成×××隊列,那麼線程池的隊列就會越來越多,有可能會撐滿內存,導致整個系統不可用,而不只是後臺任務出現問題.
2.5 線程池的監控
如果在系統中大量使用線程池,則有必要對線程池進行監控,方便在出現問題時,可以根據線程池的使用狀況快速定位問題.可通過線程池提供的參數進行監控,在監控線程池的時候可以使用以下屬性:
- taskCount:線程池需要執行的任務數量
- completedTaskCount:線程池在運行過程中已完成的任務數量,小於或等於taskCount。
- largestPoolSize:線程池裏曾經創建過的最大線程數量.通過這個數據可以知道線程池是否曾經滿過.如該數值等於線程池的最大大小,則表示線程池曾經滿過.
- getPoolSize:線程池的線程數量.如果線程池不銷燬的話,線程池裏的線程不會自動銷燬,所以這個大小隻增不減.
- getActiveCount:獲取活動的線程數.
通過擴展線程池進行監控.可以通過繼承線程池來自定義線程池,重寫線程池的
beforeExecute、afterExecute和terminated方法,也可以在任務執行前、執行後和線程池關閉前執行一些代碼來進行監控.例如,監控任務的平均執行時間、最大執行時間和最小執行時間等.
這幾個方法在線程池裏是空方法.
protected void beforeExecute(Thread t, Runnable r) { }
2.6 線程池的狀態
1.當線程池創建後,初始爲 running 狀態
2.調用 shutdown 方法後,處 shutdown 狀態,此時不再接受新的任務,等待已有的任務執行完畢
3.調用 shutdownnow 方法後,進入 stop 狀態,不再接受新的任務,並且會嘗試終止正在執行的任務。
4.當處於 shotdown 或 stop 狀態,並且所有工作線程已經銷燬,任務緩存隊列已清空,線程池被設爲 terminated 狀態。
#總結
java 線程池有哪些關鍵屬性?
- corePoolSize 到 maximumPoolSize 之間的線程會被回收,當然 corePoolSize 的線程也可以通過設置而得到回收(allowCoreThreadTimeOut(true))。
- workQueue 用於存放任務,添加任務的時候,如果當前線程數超過了 corePoolSize,那麼往該隊列中插入任務,線程池中的線程會負責到隊列中拉取任務。
- keepAliveTime 用於設置空閒時間,如果線程數超出了 corePoolSize,並且有些線程的空閒時間超過了這個值,會執行關閉這些線程的操作
- rejectedExecutionHandler 用於處理當線程池不能執行此任務時的情況,默認有拋出 RejectedExecutionException 異常、忽略任務、使用提交任務的線程來執行此任務和將隊列中等待最久的任務刪除,然後提交此任務這四種策略,默認爲拋出異常。
##線程池中的線程創建時機? - 如果當前線程數少於 corePoolSize,那麼提交任務的時候創建一個新的線程,並由這個線程執行這個任務;
- 如果當前線程數已經達到 corePoolSize,那麼將提交的任務添加到隊列中,等待線程池中的線程去隊列中取任務;
- 如果隊列已滿,那麼創建新的線程來執行任務,需要保證池中的線程數不會超過 maximumPoolSize,如果此時線程數超過了 maximumPoolSize,那麼執行拒絕策略。
##任務執行過程中發生異常怎麼處理?
如果某個任務執行出現異常,那麼執行任務的線程會被關閉,而不是繼續接收其他任務。然後會啓動一個新的線程來代替它。
##什麼時候會執行拒絕策略?
- workers 的數量達到了 corePoolSize,任務入隊成功,以此同時線程池被關閉了,而且關閉線程池並沒有將這個任務出隊,那麼執行拒絕策略。這裏說的是非常邊界的問題,入隊和關閉線程池併發執行,讀者仔細看看 execute 方法是怎麼進到第一個 reject(command) 裏面的。
- workers 的數量大於等於 corePoolSize,準備入隊,可是隊列滿了,任務入隊失敗,那麼準備開啓新的線程,可是線程數已經達到 maximumPoolSize,那麼執行拒絕策略。
參考
<<碼出高效>>