文章目录
- 一、什么是线程池预热?
- [二、Demo:不开启预热 vs 开启预热](#二、Demo:不开启预热 vs 开启预热)
- 三、测试代码
- 四、对比图(示例)
- 五、什么时候必须使用线程池预热?
- 六、总结
在高并发或低延迟系统中,"冷启动"(Cold Start)常常导致 首批请求延迟飙升 :线程池第一次创建线程、类第一次加载、JIT 未优化好......导致看似正常的接口突然「首条请求特别慢」。
一、什么是线程池预热?
线程池第一次执行任务时会:
- 创建核心线程
- 加载类
- JIT 编译热点方法
- 建立资源连接(DB / Redis / RPC)
这些都需要时间,因此第一次执行任务一般比后续慢很多。
线程池预热(warm-up)就是:
✔ 在系统启动时提前创建线程
✔ 并让线程执行一次轻量任务
使其进入 已加载、已编译、已就绪 的状态。
二、Demo:不开启预热 vs 开启预热
我们做两个测试:
- 未预热 → 首次任务延迟明显更高
- 预热后 → 首次任务也能保持正常速度
三、测试代码
以下代码包含两个方法:
① testWithoutWarmUp() 未预热
② testWithWarmUp() 已预热
完整可运行 Demo:ThreadPoolWarmUpDemo.java
java
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
public class ThreadPoolWarmUpDemo {
public static void main(String[] args) throws Exception {
System.out.println("========== 未开启预热,测试开始 ==========");
testWithoutWarmUp();
System.out.println("\n========== 开启预热后,测试开始 ==========");
testWithWarmUp();
}
/** 用于模拟"第一次慢,后面快" */
private static final AtomicBoolean firstRun = new AtomicBoolean(true);
/** 模拟核心业务逻辑 */
private static void mockBizWork() {
try {
if (firstRun.compareAndSet(true, false)) {
// 只在第一次执行时慢
Thread.sleep(50);
} else {
// 后续任务很快
Thread.sleep(2);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/** 创建一个全新的线程池(关键) */
private static ThreadPoolExecutor newExecutor() {
return new ThreadPoolExecutor(
4, 4,
60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>()
);
}
/** 未开启线程池预热 */
public static void testWithoutWarmUp() throws Exception {
ThreadPoolExecutor executor = newExecutor();
firstRun.set(true);
long start = System.currentTimeMillis();
Future<Long> future = executor.submit(() -> {
long s = System.currentTimeMillis();
mockBizWork();
return System.currentTimeMillis() - s;
});
long firstCost = future.get();
long totalCost = System.currentTimeMillis() - start;
System.out.println("第一次任务耗时 = " + firstCost + " ms");
System.out.println("整体耗时(含线程创建)= " + totalCost + " ms");
executor.shutdown();
}
/** 开启预热 */
public static void testWithWarmUp() throws Exception {
ThreadPoolExecutor executor = newExecutor();
firstRun.set(true);
System.out.println(">>> 开始预热核心线程...");
executor.prestartAllCoreThreads();
// 预热任务:吃掉"第一次慢"
for (int i = 0; i < 4; i++) {
executor.submit(ThreadPoolWarmUpDemo::mockBizWork).get();
}
System.out.println(">>> 预热完成!");
long start = System.currentTimeMillis();
Future<Long> future = executor.submit(() -> {
long s = System.currentTimeMillis();
mockBizWork();
return System.currentTimeMillis() - s;
});
long cost = future.get();
long totalCost = System.currentTimeMillis() - start;
System.out.println("第一次任务耗时(已预热)= " + cost + " ms");
System.out.println("整体耗时(线程已就绪)= " + totalCost + " ms");
executor.shutdown();
}
}

四、对比图(示例)
| 场景 | 首次任务耗时 | 备注 |
|---|---|---|
| ❄️ 未预热 | 55 ms | 包含线程创建、类加载等 |
| 🔥 已预热 | 3 ms | 线程已创建、JIT 已编译,几乎"秒回" |
五、什么时候必须使用线程池预热?
以下场景建议开启:
✔ 高并发系统(交易、支付、秒杀)
首个请求慢会直接影响用户体验和系统稳定性。
✔ 微服务自动扩容(K8S / Spring Cloud)
Pod 刚拉起来就被流量打满,如果没预热会导致:
- 错误率突然增加
- P99 延迟飙升
- 下游熔断
✔ 接口对响应时间敏感(推荐、风控、广告)
冷启动会影响模型推理链路整体延迟。
六、总结
线程池预热很简单,但效果极其明显。
👉 不预热 = 首次任务慢几十毫秒
👉 预热后 = 首次任务几毫秒完成
通过:
java
executor.prestartAllCoreThreads();
executor.submit(warmTask);
即可大幅减少冷启动延迟,保证服务稳定性和低延迟能力。