自定義ThreadPoolExecutor線程池 自定義線程池需要遵循的規則 【1】線程池大小的設置 1、計算密集型: 顧名思義就是應用需要非常多的CPU計算資源,在多核CPU時代,我們要讓每一個CPU核心都參與計算,將CPU的性能充分利用起來,這樣才算是沒有浪費伺服器配置,如果在非常好的伺服器配置 ...
自定義ThreadPoolExecutor線程池
自定義線程池需要遵循的規則
【1】線程池大小的設置
1、計算密集型:
顧名思義就是應用需要非常多的CPU計算資源,在多核CPU時代,我們要讓每一個CPU核心都參與計算,將CPU的性能充分利用起來,這樣才算是沒有浪費伺服器配置,如果在非常好的伺服器配置上還運行著單線程程式那將是多麼重大的浪費。對於計算密集型的應用,完全是靠CPU的核數來工作,所以為了讓它的優勢完全發揮出來,避免過多的線程上下文切換,比較理想方案是:
線程數 = CPU核數+1,也可以設置成CPU核數*2,但還要看JDK的版本以及CPU配置(伺服器的CPU有超線程)。
一般設置CPU * 2即可。
2、IO密集型
我們現在做的開發大部分都是WEB應用,涉及到大量的網路傳輸,不僅如此,與資料庫,與緩存間的交互也涉及到IO,一旦發生IO,線程就會處於等待狀態,當IO結束,數據準備好後,線程才會繼續執行。因此從這裡可以發現,對於IO密集型的應用,我們可以多設置一些線程池中線程的數量,這樣就能讓在等待IO的這段時間內,線程可以去做其它事,提高併發處理效率。那麼這個線程池的數據量是不是可以隨便設置呢?當然不是的,請一定要記得,線程上下文切換是有代價的。目前總結了一套公式,對於IO密集型應用:
線程數 = CPU核心數/(1-阻塞繫數) 這個阻塞繫數一般為0.8~0.9之間,也可以取0.8或者0.9。
【2】線程池參數的相關配置
一定不要選擇沒有上限限制的配置項
比如,Executors.newCachedThreadPool的設置與無界隊列的設置因為某些不可預期的情況,線程池會出現系統異常,導致線程暴增的情況或者任務隊列不斷膨脹,記憶體耗盡導致系統崩潰和異常。
儘量採用自定義的拒絕策略
自定義拒絕策略可以實現RejectedExecutionHandler介面;
JDK自帶的拒絕策略如下:
AbortPolicy:直接拋出異常阻止系統正常工作;
CallerRunsPolicy:只要線程池未關閉,該策略直接在調用者線程中,運行當前被丟棄的任務;
DiscardOldestPolicy:丟棄最老的一個請求,嘗試再次提交當前任務;
DiscardPolicy:丟棄無法處理的任務,不給予任何處理;
【3】利用Hook
利用Hook,留下線程池執行軌跡:
ThreadPoolExecutor提供了protected類型可以被覆蓋的鉤子方法,允許用戶在任務執行之前會執行之後做一些事情。我們可以通過它來實現比如初始化ThreadLocal、收集統計信息、如記錄日誌等操作。這類Hook如beforeExecute和afterExecute。另外還有一個Hook可以用來在任務被執行完的時候讓用戶插入邏輯,如rerminated 。
如果hook方法執行失敗,則內部的工作線程的執行將會失敗或被中斷。
我們可以使用beforeExecute和afterExecute來記錄線程之前前和後的一些運行情況,也可以直接把運行完成後的狀態記錄到ELK等日誌系統.
【4】關閉線程池
內容當線程池不在被引用並且工作線程數為0的時候,線程池將被終止。我們也可以調用shutdown來手動終止線程池。如果我們忘記調用shutdown,為了讓線程資源被釋放,我們還可以使用keepAliveTime和allowCoreThreadTimeOut來達到目的!
當然,穩妥的方式是使用虛擬機Runtime.getRuntime().addShutdownHook方法,手工去調用線程池的關閉方法!