
注意!注意!注意!重要的事說三遍
系統性能優化的幾種常用手段是異步和緩存。是以我們常常使用線程池異步處理一些業務。
線程池的使用還是相對比較簡單的,首先建立一個線程池,然後通過execute或submit執行任務。
但魔鬼往往藏于細節之中,稍有不慎就會出錯。本文将會詳細總結線程池容易出錯的五大坑
一、拒絕政策參數知多少 二、拒絕政策使用不當,系統阻塞不可用 三、多任務get()異常時,結果擷取有誤 四、ThreadLocal與線程池搭配使用,上下文缺失 五、父子任務共用同一線程池,系統“饑餓”死鎖
以下為線程池的核心流程【具體内容參考:線程池原理】
我們都知道,當任務過多,線程池處理不過來時會被拒絕,進入拒絕政策
通過實作RejectedExecutionHandler,就可以作為線程池的拒絕政策使用。
目前官方提供了四種拒絕政策,分别為:
CallerRunsPolicy:由任務調用方執行
AbortPolicy:抛出異常,同樣也是由任務調用方處理異常
DiscardPolicy:丢棄目前任務
DiscardOldestPolicy:丢棄隊列中最老的任務,并執行目前任務
線程池有execute和submit兩種方法執行任務:
execute執行我們最原始的任務;
而submit則不同,先是将我們最原始的任務封裝成FutureTask任務,然後将FutureTask任務交由execute執行
線程池拒絕政策中Runnable r就是execute執行的任務,是以當使用r時就要注意它是我們最原始的任務還是FutureTask任務
前面我們講到submit方法執行任務時,線程池會先封裝任務到FutureTask中,然後我們通過FutureTask的get()方法擷取任務處理的結果
【具體内容參考:一張動圖,徹底懂了execute和submit】
Possible state transitions: NEW -> COMPLETING -> NORMAL(任務執行完成) NEW ->COMPLETING -> EXCEPTIONAL(任務抛出異常) NEW -> CANCELLED(任務被取消) NEW -> INTERRUPTING -> INTERRUPTED(任務被打斷)
FutureTask在被建立時狀态為NEW,任務執行到某個階段就會修改成相應狀态,直到達到最終态。
FutureTask根據狀态變更來辨別任務執行進度的,是以get()方法也是在狀态達到最終态(任務執行成果/異常/被取消/被打斷)時才能傳回結果,否則挂起目前線程等待到達最終态。
問題原因:
1、當任務通過submit方法執行時,會建立FutureTask(此時狀态為NEW)
2、任務被拒絕且拒絕政策為丢棄任務(DiscardOleddestPolicy或DiscardPolicy)時,任務直接被線程池丢棄(此時狀态仍為NEW)
3、當執行get()方法時,由于任務一直處于NEW狀态,沒有達到最終态,線程會一直處于阻塞狀态
解決方案:
問題原因在于:任務無法變成最終态,導緻阻塞。
是以我們可以重寫rejectedExecution方法,将任務置為最終态
FutureTask的cancel方法可以将任務狀态置為CANCELLED或INTERRUPTED
submit方法中,futureTask會捕獲異常,在get()時抛出。
若批量執行多個方法,且for循環get()結果時,捕獲異常要在循環内,而不是循環外。否則會影響其他任務的結果輸出
捕獲異常在循環外,當一個任務get異常時,後續其他任務就不能再擷取結果
是以在循環内捕獲異常,各個任務互相不受影響
ThreadLocal的使用一般都是這幾個方法:
為防止記憶體洩漏,在使用完ThreadLocal後都會調用remove()清除資料
問題描述:
1、當任務需要調用方線程的ThreadLocal資訊時,通用方式就是将調用方ThreadLocal資訊指派到執行任務的線程中,在任務執行結束後調用remove()清除資料
2、同時任務恰好被線程池拒絕,且使用的拒絕政策是CallerRunsPolicy時,任務會被調用方線程執行。
3、若此時任務執行結束後仍調用remove()清除資料,清除的就會是調用方的ThreadLocal資料。
調用方ThreadLocal資料被清除,資料丢失在工作中将會是災難性的。
問題出現的原因是任務由于被拒絕,導緻誤删除了調用方ThreadLocal資料
是以可以在任務執行時判斷執行線程是否為調用方線程。
若是則不用set()複制和remove()清空資料
待執行的任務通過重寫process方法,并根據sameThread判斷是否和主線程一緻,一緻則不重複設定相同的threadLocal和删除threadLocal
A方法調用B方法,AB方法稱為父子任務。
當他們都被同一個線程池執行時,一定條件下會出現以下場景:
1、父任務擷取到線程池線程執行,而子任務則被暫存到隊列中
2、當父任務占滿了線程池所有的線程,等待子任務傳回結果後,結束父任務
3、此時子任務由于在隊列中,一直不能等到線程來處理,導緻不能從隊列中釋放
4、父子任務互相等待,進而造成“饑餓”死鎖
我們舉一個簡單例子:
現在父子任務都被同一個線程池進行調用,整個流程為(如圖所示):
1、線程池建立核心線程,并執行A方法 2、執行到B方法時,将B交給線程池執行,由于沒有多餘線程,是以暫存隊列 3、A任務等待B任務執行完,B任務等待A任務釋放線程。進而互相等待,造成“饑餓”死鎖
問題原因在于互相等待,是以隻要保證類似的父子任務不要被同一線程池執行即可
------The End------
如果這個辦法對您有用,或者您希望持續關注,也可以掃描下方二維碼或者在微信公衆号中搜尋【碼路無涯】