服務器應用面臨大量短任務。逐請求創建線程開銷巨大(創建/銷毀時間超過業務處理),且無限制創建導致內存耗盡或上下文切換風暴。線程池通過線程複用攤銷創建成本、固定線程數防止資源耗盡、任務排隊實現削峰填谷。
「顯而易見」的實現(調用者等待可用線程)會阻塞提交線程,違背異步初衷。正確模式:
LinkedList<Runnable> 工作隊列execute(Runnable) 入隊並 notify()wait() 取任務執行public class WorkQueue {
private final LinkedList<Runnable> queue = new LinkedList<>();
private final PoolWorker[] threads;
public WorkQueue(int n) { threads = new PoolWorker[n]; for (int i=0;i<n;i++) threads[i].start(); }
public synchronized void execute(Runnable r) { queue.addLast(r); notify(); }
private class PoolWorker extends Thread {
public void run() {
Runnable r;
while (true) {
synchronized(queue) {
while (queue.isEmpty()) { try { queue.wait(); } catch (InterruptedException ignored) {} }
r = (Runnable) queue.removeFirst();
}
try { r.run(); } catch (RuntimeException e) { /* log */ }
}
}
}
}
為何用 notify() 而非 notifyAll():單消費者模式下 notify() 避免驚群效應,上下文切換更少。前提是所有 Worker 等待同一條件(隊列非空),滿足 notify() 安全使用條件。
| 風險 | 成因 | 對策 |
|---|---|---|
| 死鎖 | 任務同步等待另一排隊任務,池線程全阻塞 | 禁止排隊任務同步等待其他排隊任務 |
| 資源耗盡 | 池過大、任務持有稀缺資源(JDBC/Socket) | 根據 CPU/IO 特性調整池大小,限制併發資源獲取 |
| 併發錯誤 | wait/notify 使用不當丟失通知 | 直接使用成熟庫 |
| 線程洩漏 | 任務拋出 RuntimeException/Error 導致 Worker 退出 | Worker run() 捕獲 Throwable,保證線程存活 |
N_cpu 或 N_cpu + 1N_cpu * (1 + WT/ST),WT=平均等待時間,ST=平均服務時間Doug Lea util.concurrent 提供生產級 PooledExecutor,已修復上述所有坑。JSR 166 將其標準化為 java.util.concurrent (JDK 1.5+)。直接使用 ExecutorService / ThreadPoolExecutor,專注業務邏輯。
Executors 工廠或 ThreadPoolExecutor 精細控制