為什麼使用線程池?
服務器應用常面臨大量短小任務。若每個請求創建一個線程(thread-per-request),創建/銷毀開銷巨大,且活動線程消耗系統資源,可能導致內存耗盡或過度上下文切換。線程池通過重用線程分攤創建開銷,消除線程創建延遲,並通過限制線程數防止資源耗盡。
使用線程池的風險
- 死鎖:除常見死鎖外,線程池特有的死鎖發生在所有線程等待隊列中任務結果,而該任務因無空閒線程無法執行。
- 資源不足:線程池過大導致內存、CPU 切換開銷過大;過小則無法充分利用 CPU。
- 並發錯誤:依賴
wait()/notify() 易出錯,建議使用成熟庫(如 util.concurrent)。
- 線程洩漏:任務拋出未捕獲的
RuntimeException 或 Error 導致線程退出未歸還池中。
- 請求過載:隊列積壓過多任務消耗資源,需採取拒絕策略。
有效使用線程池的準則
- 不要提交同步等待其他任務結果的任務。
- 對長時間操作(如 I/O)設置超時,失敗則重新排隊。
- 理解任務特性(CPU 密集型 vs I/O 密集型),針對不同任務類型配置多個線程池。
線程池大小設置
最佳大小取決於處理器數 N 與任務性質:
- CPU 密集型:
N 或 N+1 線程達到最大 CPU 利用率。
- I/O 密集型:需超過 N,公式
N * (1 + WT/ST),其中 WT 為等待時間,ST 為服務時間。
同時考慮內存、文件句柄、數據庫連接等系統資源限制。
常用線程池類型
1. newCachedThreadPool
- 可緩存線程池,線程數幾乎無限制(Integer.MAX_VALUE)。
- 空閒 1 分鐘自動終止,新任務再創建。
- 需控制任務數量,防止系統癱瘓。
2. newFixedThreadPool
- 固定線程數,任務提交創建線程直到達到上限,多餘任務入隊列。
- 優點:提高效率,節省創建開銷。缺點:空閒時不釋放線程,佔用資源。
- 建議大小:
Runtime.getRuntime().availableProcessors()。
3. newSingleThreadExecutor
- 單線程化 Executor,保證任務按指定順序(FIFO、LIFO、優先級)執行。
- 線程異常結束會自動替換,確保順序執行。
4. newScheduledThreadPool
- 定長線程池,支持定時與周期性任務。
- 示例:延遲 3 秒執行,或延遲 1 秒後每 3 秒執行一次。