這裏主要總結池子的管理線程、處理任務的流程,以採用LinkedBlockingQueue corePoolSize=maxPoolSzie=5 KeepAliveTime=0L 爲例解說:
ThreadPoolExecutor .execute(Runnable),向池中存放任務;隨着execute 到corePoolsize任務時,池初始化啓動了corePoolsize個線程,池子維護這5個線程;這5個線程監控Queue等待take任務。
當corePoolSzie線程就緒後,新進任務直接queue.offer();
/**
* Main run loop
*/
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}
池中線程,一直處於dotask ...queue.take()...dotask的循環之中。而queue.take()和poll()的區別就是直到取到有效element才返回;
引用別人解說:
所有 BlockingQueue
都可用於傳輸和保持提交的任務。可以使用此隊列與池大小進行交互:
排隊有三種通用策略:
-
直接提交。工作隊列的默認選項是
SynchronousQueue
,它將任務直接提交給線程而不保持它們。在此,如果不存在可用於立即運行任務的線程,則試圖把任務加入隊列將失敗,因此會構造一個新的線程。此策略可以避免在處理可能具有內部依賴性的請求集時出現鎖。直接提交通常要求無界 maximumPoolSizes 以避免拒絕新提交的任務。當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性。 -
無界隊列。使用無界隊列(例如,不具有預定義容量的
LinkedBlockingQueue
)將導致在所有 corePoolSize 線程都忙時新任務在隊列中等待。這樣,創建的線程就不會超過 corePoolSize。(因此,maximumPoolSize 的值也就無效了。)當每個任務完全獨立於其他任務,即任務執行互不影響時,適合於使用無界隊列;例如,在 Web 頁服務器中。這種排隊可用於處理瞬態突發請求,當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性。 -
有界隊列。當使用有限的 maximumPoolSizes 時,有界隊列(如
ArrayBlockingQueue
)有助於防止資源耗盡,但是可能較難調整和控制。隊列大小和最大池大小可能需要相互折衷:使用大型隊列和小型池可以最大限度地降低 CPU 使用率、操作系統資源和上下文切換開銷,但是可能導致人工降低吞吐量。如果任務頻繁阻塞(例如,如果它們是 I/O 邊界),則系統可能爲超過您許可的更多線程安排時間。使用小型隊列通常要求較大的池大小,CPU 使用率較高,但是可能遇到不可接受的調度開銷,這樣也會降低吞吐量。
例子一:使用直接提交策略,也即SynchronousQueue。
首先SynchronousQueue是無界的,也就是說他存數任務的能力是沒有限制的,但是由於該Queue本身的特性,在某次添加元素後必須等待其他線程取走後才能繼續添加。在這裏不是核心線程便是新創建的線程,但是我們試想一樣下,下面的場景。
LinkedBlockingQueue
OK,此時任務變加入隊列之中了,那什麼時候纔會添加新線程呢?
這裏就很有意思了,可能會出現無法加入隊列嗎?不像SynchronousQueue那樣有其自身的特點,對於無界隊列來說,總是可以加入的(資源耗盡,當然另當別論)。換句說,永遠也不會觸發產生新的線程!corePoolSize大小的線程數會一直運行,忙完當前的,就從隊列中拿任務開始運行。所以要防止任務瘋長,比如任務運行的實行比較長,而添加任務的速度遠遠超過處理任務的時間,而且還不斷增加,如果任務內存大一些,不一會兒就爆了,呵呵。
總結:
- ThreadPoolExecutor的使用還是很有技巧的。
- 使用無界queue可能會耗盡系統資源。
- 使用有界queue可能不能很好的滿足性能,需要調節線程數和queue大小
- 線程數自然也有開銷,所以需要根據不同應用進行調節。
- 數量大,但是執行時間很短
- 數量小,但是執行時間較長
- 數量又大執行時間又長
- 除了以上特點外,任務間還有些內在關係
RejectedExecutionException