執行緒複用:執行緒池筆記
執行緒複用:執行緒池
執行緒池總概
什麼是執行緒池?
接觸過JDBC的人,一定聽說過資料庫連線池(比如,c3p0、Druid等)。其實在我的理解中,兩者是差不多的。不過執行緒池中放的是執行緒而已。
執行緒是一種輕量級工具,但其建立與關閉都需要花費一定的時間。而且大量的執行緒會搶佔記憶體資源。盲目的大量資源會對系統造成極大的壓力。
執行緒池,中有一定數量的活躍執行緒。建立執行緒變成了從執行緒池中獲得空閒執行緒;關閉執行緒變成了向執行緒池歸還執行緒。
JDK對於執行緒池的支援
Java通過Executors提供五種執行緒池,分別為:
newCachedThreadPool
newFixedThreadPool
建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。newScheduledThreadPool
建立一個定長執行緒池,支援定時及週期性任務執行。newSingleThreadExecutor
建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。newSingleThreadScheduledExcutor
建立單執行緒化的執行緒池,支援定時及週期性任務執行。
執行緒池的使用
首先是簡單使用,這個沒有什麼特殊之處。
只需記得newFixedThreadPool
建立的是定長的執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。
而newCachedThreadPool
建立的執行緒池為無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的執行緒,而不用每次新建執行緒。
定時任務
newScheduledThreadPool
支援定時及週期性任務執行,查看了其原始碼,主要有以下三種方法:
- schedule():在給定時間,對任務進行排程;
- scheduleAtFixedRate() 和 scheduleWithFixedDelay():對任務進行週期性排程,但兩者有所區別。
scheduleAtFixedRate() 和 scheduleWithFixedDelay() 的區別
- 兩種排程的區別:
- FixedRate 方式:以上一個任務開始執行時間為起點,在之後的延遲時間後,呼叫下一次任務。
- FixedDelay 方式:上一個任務結束後,再經過延遲時間進行任務排程。
- 若任務執行時間超過排程時間,
- FixedRate 方式:若排程時間過短,那麼任務會在上一個任務結束後立刻呼叫(不會出現任務堆疊的現場)。
- FixedDelay 方式:會嚴格按照
任務間隔時間 = 排程時間 + 任務執行時間
。
如果任務遇到異常,那麼後續的所有子任務都會停止排程。因此必須保證,異常被及時處理,為週期性任務的穩定排程提供條件。
關於執行緒池的記錄
拒絕策略
建立執行緒池的核心類 ThreadPoolExecutor 有一個引數指定了拒絕策略。拒絕策略,是系統超負荷執行時的補救措施,通常是由於壓力太大而引起的,也就是執行緒池中的執行緒已經用完了且等待佇列已經排滿了。
JDK 提供了四種拒絕策略:
- AbortPolicy 策略:直接丟擲異常,阻止系統正常工作。
- CallerRnsPolicy 策略:只要執行緒池未關閉,將直接在呼叫者執行緒中執行被丟棄的任務。這種做法不會真的丟棄任務,但是任務提交執行緒的效能將急劇下降。
- DiscardOldestPolicy 策略:丟棄最老的一個請求,也就是即將被執行的任務(處於等待佇列的隊頭),並嘗試再次提交當前任務。
- DiscardPolicy 策略:直接丟掉無法處理的任務。
- 自定義策略:自己擴充套件 RejectedExecutionHandler 介面。
執行緒擴充套件
ThreadPoolExecutor
是一個可擴充套件的執行緒池,有beforeExecute()
、afterExecute()
和terminated()
能夠對執行緒進行控制。
protected void beforeExecute(Thread t, Runnable r) { }
protected void afterExecute(Runnable r, Throwable t) { }
protected void terminated() { }
這是三個protected
的空方法,擺明了可以讓子類擴充套件。
* 在執行任務的執行緒中將呼叫beforeExecute
和afterExecute
等方法,在這些方法中還可以新增日誌、計時、監視或者統計資訊收集的功能。
* 無論是正常執行,還是丟擲異常,都會呼叫afterExecute
。但是,如果丟擲Eorror,將不會呼叫該方法;或者beforeExecute
丟擲一個RuntimeException
,則任務將不被執行,即該方法也不會被呼叫。
* 關於terminated
,線上程池完成關閉時(就是在所有任務已經完成且所有工作者執行緒已經關閉),用來釋放Executor
在生命週期裡分配的各種資源,此外還能執行資訊通知、日誌記錄等功能。
補充
使用執行緒池被”吃”掉了異常堆疊資訊
在使用執行緒池提交執行緒時,可能會發生異常堆疊資訊被”吃”掉的現象,而解決方法:- 放棄submit(),改用execute()。
獲取submit()方法返回類的get()方法。
Future future = pools.submit(new Thread()); future.get();
擴充套件 ThreadPoolExecutor 執行緒池,讓其在排程任務前,先儲存提交任務執行緒的堆疊訊息(就是重寫執行緒池執行緒的呼叫方法)。
自定義執行緒:ThreadFactory
這個介面只有一個方法newThread(Runnable r)
,主要是由執行緒池呼叫新建執行緒。優化執行緒池執行緒數量
在《Java Concurrency in Practice》書中給了一個估算執行緒池大小的經驗公式(同時,在Java中,可以通過Runtime.getRuntime().availableProcessors
獲取可用的CPU數量。)。
Ncpu = CPU數量
Ucpu = 目標CPU的使用率,0 <= Ucpu <= 1
W/C = 等待時間與計算時間的比率
所以,最優的執行緒池大小為:
Nthreads = Ncpu * Ucpu * ( 1 + W/C )
參考資料
- 《實戰Java高併發程式設計》(葛一鳴 郭超 著)