iT邦幫忙

2026 iThome 鐵人賽

0
Build on Google AI

把Google AI當夥伴打造個人高效Coding工作流系列 第 19 篇

第十九天ThreadPoolExecutor七大核心參數、工作流程與自訂飽和拒絕策略

  • 分享至 

  • xImage
  •  

一、 為什麼生產環境嚴禁使用 Executors 快速建立執行緒池?
阿里巴巴Java開發手冊與各大企業架構規範中明確列為禁止事項:

  • Executors.newFixedThreadPool() 與 newSingleThreadExecutor():
  1. 底層使用無界佇列 LinkedBlockingQueue(預設容量為 Integer.MAX_VALUE)。
  2. 當任務堆積速度大於處理速度時,佇列會無限膨脹,最終直接引發 OutOfMemoryError: Java heap space。
  • Executors.newCachedThreadPool():
  1. 允許的最大執行緒數為 Integer.MAX_VALUE。
  2. 遇到突發大量流量時,會瘋狂建立大量作業系統原生執行緒,導致系統記憶體耗盡並引發 CPU 劇烈 Context Switch 崩潰。

二、 ThreadPoolExecutor七大核心建構參數剖析

  • corePoolSize(核心執行緒數):
    執行緒池中常駐的最小工作執行緒數量,即使這些執行緒處於閒置狀態也不會被銷毀(除非設定了 allowCoreThreadTimeOut(true))。
  • maximumPoolSize(最大執行緒數):
    執行緒池所能容納的執行緒數量上限(包含核心與非核心執行緒)。
  • keepAliveTime(非核心執行緒存活時間):
    當執行緒數量大於 corePoolSize 時,多出來的閒置非核心執行緒在銷毀前等待新任務的最長時間。
  • unit(時間單位):
    keepAliveTime 的時間單位(如 TimeUnit.SECONDS、TimeUnit.MILLISECONDS)。
  • workQueue(任務阻塞佇列):
    用於存放等待執行的 Runnable 任務的阻塞佇列(如 ArrayBlockingQueue、LinkedBlockingQueue)。
  • threadFactory(執行緒工廠):
    用於建立新執行緒的工廠介面。務必自訂,為每個執行緒指定具備業務語義的名稱(Thread Name)與未捕獲例外處理器,否則日後線上查排 Log 會全是無意義的 pool-1-thread-1。
  • handler(拒絕策略 / 飽和策略):
    當任務佇列已滿、且運行的執行緒數已達到 maximumPoolSize 時,對新提交任務的處理處置機制。

三、 執行緒池任務提交流程(極易混淆的核心順序)
當呼叫 executor.execute(task) 提交新任務時,JVM 的判定順序並非「先建滿最大執行緒才進佇列」,而是嚴格遵循以下流水線:

  1. 檢查核心數:若正在運行的執行緒數小於 corePoolSize,立即建立新的工作執行緒(Worker)執行該任務,即便此時有其他核心執行緒正處於閒置中。
  2. 塞入佇列:若運行中的執行緒數已達 corePoolSize,嘗試將任務塞入 workQueue 阻塞佇列中等待。
  3. 建立救火非核心執行緒:若 workQueue 已滿且無法再放入,但當前運行執行緒數仍小於 maximumPoolSize,立即建立非核心工作執行緒執行該任務。
  4. 觸發拒絕策略:若執行緒數已達 maximumPoolSize 且 workQueue 也已滿,執行緒池宣布飽和,呼叫 RejectedExecutionHandler.rejectedExecution() 進行拒絕。

四、 JDK 內建四大飽和拒絕策略

  • AbortPolicy:
    • 直接拋出RejectedExecutionException運行時例外。
    • 適用場景:核心關鍵業務,要求快速失敗並依賴外層補償機制。
  • CallerRunsPolicy:
    • 不拋例外,也不丟棄任務,而是直接把任務交回給呼叫execute()的那個執行緒去執行。
    • 架構價值:形成天然的背壓機制。因為呼叫端執行緒(如 Tomcat 的 HTTP 線程)跑去執行這個耗時任務,它提交後續新任務的步調就會被迫大幅慢下來,給執行緒池喘息消化的時間。
  • DiscardPolicy:
    • 直接將該任務丟棄,完全不給通知、不拋例外。
    • 適用場景:非關鍵業務、允許資料遺失的情境。
  • DiscardOldestPolicy:
    • 丟棄workQueue佇列中最前端的未執行任務,並嘗試重新提交當前新任務。

五、核心參數容量估算公式(僅作基線參考)

  • CPU 密集型(大量數學計算、加解密、圖像處理):
    • 執行緒大部分時間都在使用 CPU,過多執行緒只會增加切換開銷。
    • 推薦值:核心數 = CPU 核心數 + 1(額外 +1 是為了防止某個執行緒偶發缺頁中斷等延遲)。
  • I/O 密集型(大量資料庫查詢、呼叫外部 HTTP API、讀寫檔案):
    • 執行緒大部分時間都在阻塞等待 I/O 回傳,CPU 空閒率高。
    • 推薦值:核心數 = CPU 核心數 × (1 + I/O 等待時間 / CPU 計算時間),實務上常抓 CPU 核心數 × 2 到 4 之間。
  • 動態調參是終極解:真正的微服務架構中,靜態公式難以應對突發流量,最佳解是將核心與最大執行緒數設定為配置中心(如 Nacos / Apollo)的動態變數,並透過 setCorePoolSize() 在運行期間平滑調優。

上一篇
第十八天Java記憶體模型、volatile可見性與指令重排、CAS與Atomic原子類
下一篇
第二十天Project Loom核心演進、結構化併發與傳統執行緒池淘汰考量
系列文
把Google AI當夥伴打造個人高效Coding工作流 共 21 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言