線程池的拒絕策略
- AbortPolicy:直接拋出異常阻止系統正常工作。
- CallerRunsPolicy:只要線程池未關閉,該策略直接在調用者線程中,運行當前被丟棄的任務。
- DiscardOldestPolicy:丟棄最老的一個請求,嘗試再次提交當前任務。
- DiscardPolicy:丟棄無法處理的任務,不給予任何處理。
- 如果需要自定義拒絕策略可以實現RejectedExecutionHandler接口
如何使用好線程池
線程個數大小的設置
計算密集型
- 顧名思義就是應用需要非常多的CPU計算資源,在多核CPU時代,我們要讓每一個CPU核心都參與計算,將CPU的性能充分利用起來,這樣才算是沒有浪費服務器配置,如果在非常好的服務器配置上還運行著單線程程序那將是多么重大的浪費
- 對于計算密集型的應用,完全是靠CPU的核數來工作,所以為了讓它的優勢完全發揮出來,避免過多的線程上下文切換,比較理想方案是: 線程數 = CPU核數+1,也可以設置成CPU核數*2,但還要看JDK的版本以及CPU配置(服務器的CPU有超線程)
IO密集型
- 就很好理解了,我們現在做的開發大部分都是WEB應用,涉及到大量的網絡傳輸,不僅如此,與數據庫,與緩存間的交互也涉及到IO,一旦發生IO,線程就會處于等待狀態,當IO結束,數據準備好后,線程才會繼續執行
- 因此從這里可以發現,對于IO密集型的應用,我們可以多設置一些線程池中線程的數量,這樣就能讓在等待IO的這段時間內,線程可以去做其它事,提高并發處理效率。那么這個線程池的數據量是不是可以隨便設置呢?當然不是的,請一定要記得,線程上下文切換是有代價的
- 目前總結了一套公式,對于IO密集型應用: 線程數 = CPU核心數/(1-阻塞系數) 這個阻塞系數一般為0.8~0.9之間,也可以取0.8或者0.9。 套用公式,對于雙核CPU來說,它比較理想的線程數就是20,當然這都不是絕對的,需要根據實際情況以及實際業務來調整:final int poolSize = (int)(cpuCore/(1-0.9))
線程池相關參數配置
- 使用線程池的時候都不要選擇沒有上限限制的配置項。 第一,我們不要去使用沒有上限的線程池和設置無界隊列! 比如,newCachedThreadPool的設置與無界隊列的設置因為某些不可預期的情況,線程池會出現系統異常,導致線程暴增的情況或者任務隊列不斷膨脹,內存耗盡導致系統崩潰和異常。 我們推薦使用自定義線程池來避免該問題,這也是在使用線程池規范的首要原則
- 合理設置線程數量、和線程空閑回收時間,根據具體的任務執行周期和時間去設定,避免頻繁的回收和創建,雖然我們使用線程池的目的是為了提升系統性能和吞吐量,但是也要考慮下系統的穩定性,不然出現不可預期問題會很麻煩
- 根據實際場景,選擇適用于自己的拒絕策略。進行補償,不要亂用JDK支持的自動補償機制!盡量采用自定義的拒絕策略去進行兜底
利用Hook嵌入你的行為
- 利用Hook,留下線程池執行軌跡: ThreadPoolExecutor提供了protected類型可以被覆蓋的鉤子方法,允許用戶在任務執行之前和執行之后做一些事情。
- 我們可以通過它來實現比如初始化ThreadLocal、收集統計信息、如記錄日志等操作。這類Hook如beforeExecute和afterExecute。
- 另外還有一個Hook可以用來在任務被執行完的時候讓用戶插入邏輯,如rerminated 。 如果hook方法執行失敗,則內部的工作線程的執行將會失敗或被中斷
線程池的關閉?
- 內容當線程池不在被引用并且工作線程數為0的時候,線程池將被終止。我們也可以調用shutdown來手動終止線程池。如果我們忘記調用shutdown,為了讓線程資源被釋放,我們還可以使用keepAliveTime和allowCoreThreadTimeOut來達到目的! 當然,穩妥的方式是使用虛擬機Runtime.getRuntime().addShutdownHook方法,手工去調用線程池的關閉方法
?