同樣提交 20 萬個簡單任務 (向 List 添加隨機數):
本文通過源碼級原理分析、JVM 線程創建開銷拆解、線程池復用機制圖解,徹底講透「為什麼線程池快」。
// === 線程池版 ===
int count = 200_000;
long start = System.currentTimeMillis();
List list = new LinkedList<>();
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(count)
);
Random random = new Random();
for (int i = 0; i < count; i++) {
pool.execute(() -> list.add(random.nextInt()));
}
pool.shutdown();
pool.awaitTermination(1, TimeUnit.DAYS);
System.out.println("線程池耗時: " + (System.currentTimeMillis() - start) + " ms"); // ~172 ms
System.out.println("列表大小: " + list.size()); // 200000
// === 手動線程版 ===
start = System.currentTimeMillis();
list = new LinkedList<>();
random = new Random();
for (int i = 0; i < count; i++) {
Thread t = new Thread(() -> list.add(random.nextInt()));
t.start();
t.join(); // 等待結束,實際是串行
}
System.out.println("手動線程耗時: " + (System.currentTimeMillis() - start) + " ms"); // ~33556 ms
| 維度 | 手動 new Thread | 線程池 |
|---|---|---|
| 線程生命週期 | 創建 → 執行 → 銷毀 (每任務一次) | 創建一次 → 循環從隊列取任務執行 → 空閒保活 |
| JVM 開銷 | 分配棧內存 (默認 1MB)、創建 Thread 對象、OS 系統調用 (pthread_create)、加入調度隊列 | 僅首次創建支付,後續復用極低成本 |
| 任務提交成本 | O(創建線程) ≈ 微秒~毫秒級 | O(入隊) ≈ 納秒級 (CAS/鎖入隊) |
| 上下文切換 | 頻繁創建/銷毀引發更多切換 | 固定線程數,切換可控 |
| 內存壓力 | 20萬線程棧 = 200GB 虛擬內存 (OOM 風險) | 固定 1 條線程,棧內存恆定 |
┌─────────────────────────────────────────────────────────────┐
│ ThreadPoolExecutor │
├─────────────────────────────────────────────────────────────┤
│ corePoolSize=1 maximumPoolSize=1 keepAlive=60s │
│ workQueue=LinkedBlockingDeque(200000) │
├─────────────────────────────────────────────────────────────┤
│ Worker Thread (單條) │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ while (!shutdown || !queue.isEmpty()) { │ │
│ │ Runnable task = queue.take(); // 阻塞取任務 │ │
│ │ task.run(); // 執行任務 │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↑ ↑
提交任務 循環復用
pool.execute() 同一線程
new Thread() → 初始化 Thread 對象、分配 ThreadLocal、生成 ID。Thread::start() → JavaThread::create() → os::create_thread()。pthread_create() → 分配內核棧、用戶棧 (默認 1MB)、設置 TLS、加入調度器運行隊列。Thread::run() → 執行 Runnable.run() → 結束 → pthread_exit() → 釋放棧內存、內核資源。每次創建/銷毀涉及用戶態↔內核態多次切換、內存分配/釋放、鎖競爭,耗時通常 50μs ~ 500μs。20 萬次 × 100μs = 20 秒,與實測 33 秒同量級 (含 join 等待開銷)。
execute() 只做 workQueue.offer(task) (CAS/鎖入隊),納秒級。take() 閒時阻塞 (Park),有任務喚醒 (Unpark) 執行,零創建開銷。// CPU 密集型:核心數 + 1 (減少上下文切換)
new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors() + 1,
Runtime.getRuntime().availableProcessors() + 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>()
);
// IO 密集型:核心數 * 2 (或更多,經驗公式:CPU核心數 * (1 + 平均等待時間/平均工作時間))
new ThreadPoolExecutor(
coreSize, maxSize, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new ThreadFactoryBuilder().setNameFormat("biz-%d").build(),
new ThreadPoolExecutor.AbortPolicy() // 或 CallerRunsPolicy 降級
);
FixedThreadPool (無界隊列 OOM)、CachedThreadPool (無界線程 OOM)、SingleThreadExecutor (無界隊列)。手動構建 ThreadPoolExecutor 明確有界隊列、拒絕策略。biz-order-process-%d,排查棧 trace 秒定業務。execute 不拋異常,需在任務內 try-catch 或繼承 ThreadPoolExecutor 重寫 afterExecute 統一捕獲。線程是昂貴資源,池化是核心思想。20 萬任務實測證明:復用快 195 倍。本質區別在於將「創建線程」的高固定成本攤攤到海量任務上。生產上拒絕 Executors、手動配置有界隊列、監控全鏈路,才是正確姿勢。
一句话心法:任務輕量且高頻 → 線程池;任務重耗時且低頻 → CompletableFuture / 虛擬線程 (JDK 21+) / 異步框架。