4. keepAliveTime 参数详解与最佳实践:非核心线程的存活时间
线程池里的线程,不能只创建不回收吧?
keepAliveTime 这个参数,就是用来控制非核心线程“闲着多久才被销毁”的。
我刚开始接触线程池时,对这个参数很不在意。总觉得设大点、设小点无所谓。
直到有一次线上服务 CPU 忽高忽低,线程数像过山车一样上下波动……
嗯,从那以后我才认真研究起这个参数来。
4.1 keepAliveTime 到底在管什么?
说白了,这个参数只针对“非核心线程”生效。
当线程池里的线程数超过了 corePoolSize,多出来的线程就是“非核心”的。
这些线程如果空闲时间超过了 keepAliveTime,就会被回收掉。
来看个例子:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
30, // 非核心线程空闲等待时间
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100)
);
上面这段代码,核心线程 5 个,最大允许 10 个。
当第 6 个任务进来时,会创建第 6 个线程——这个就是非核心线程。
如果这个线程空闲了 30 秒,那就拜拜了。
除非你调用了
allowCoreThreadTimeOut(true)。
4.2 设置依据:不是拍脑袋就能定的
这个值到底设多少?
不能太短,也不能太长。我踩过的坑就在这里。
| 设置倾向 | 后果 | 典型场景 |
|---|---|---|
| 太短(如 1 秒) | 线程频繁创建销毁,系统开销大 | 突发性任务,间隔较长 |
| 太长(如 10 分钟) | 空闲线程占内存,浪费资源 | 任务稀少,但单次执行时间长 |
| 适中的(如 30~60 秒) | 既能复用线程,又不浪费资源 | 大部分业务场景 |
我个人习惯这样判断:
先看看任务到达的平均间隔时间。
如果任务平均每 5 秒来一个,keepAliveTime 设个 10~15 秒就够用。
如果任务平均每分钟才来一个,那设 30 秒也行,甚至直接用核心线程就够了。
4.3 如何避免频繁创建销毁
这个问题的本质,其实是“不要让线程刚创建就被回收”。
你想想看,如果一个任务来了,创建了一个线程,处理了 50 毫秒就完事了。
结果紧接着又来一个任务,又要创建线程……
为什么会这样?
因为 keepAliveTime 设得太短了!
线程刚完成任务,还没来得及等下一个任务,就被回收了。
避坑指南:
我曾经在一个高并发场景下,把 keepAliveTime 设成了 2 秒。
结果线上日志显示每秒都有几十次线程创建和销毁。
CPU 的上下文切换开销比处理任务还高。
后来我改成 30 秒,线程创建频率直接降为 0。
核心线程已经够用了,非核心线程几乎没创建过。
4.3.1 加一点“缓冲”效果
我的建议是:
把 keepAliveTime 设为任务执行时间均值的 2~5 倍。
这样即使任务偶有波动,线程也能留下来等一等。
比如,每个任务平均执行 200 毫秒,那 keepAliveTime 设 1~2 秒就差不多。
4.3.2 配合核心线程数一起调
如果核心线程数已经能覆盖大部分并发量,非核心线程很少用上。
那 keepAliveTime 设大一点也无伤大雅。
反正非核心线程压根不创建,回收个啥?
但如果核心线程数设得比较少,比如 2 个,任务又密集。
那 keepAliveTime 就得仔细算算了。
设置太短,非核心线程会在“创建→执行→回收”之间来回跑。
这会带来不小的性能开销,尤其在 Linux 上,线程创建的成本比 Java 对象创建高得多。
4.4 核心线程也能超时?
刚才说过,默认情况下核心线程是不会被回收的。
但有的场景下,比如夜间流量很低,你还让 10 个核心线程闲着?
不想浪费内存,怎么办?
ThreadPoolExecutor 提供了一个方法:
executor.allowCoreThreadTimeOut(true);
打开这个开关后,核心线程也会受 keepAliveTime 控制。
空闲超过指定时间,一样被回收。
我什么时候会用?
比如定时任务线程池,白天忙、晚上闲。
我会设置 allowCoreThreadTimeOut(true),并给一个合理的 keepAliveTime。
晚上空闲时线程自动回收,白天任务来了再创建。
否则会抛出 IllegalArgumentException。
4.5 一张图理解 keepAliveTime 的决策逻辑
4.6 我的 keepAliveTime 实践建议
说了这么多,给个硬核结论吧:
- 普通业务接口: 30~60 秒。任务间隔一般在秒级,留够缓冲。
- 定时任务/批量处理: 如果任务很密集,几分钟或者干脆不用非核心线程。
- 流量有明显波峰波谷: 配合 allowCoreThreadTimeOut(true),设 60~120 秒。
- 内存敏感的场景: 设短一点,10~15 秒。别让空闲线程占着内存不放。
观察线程池的 activeCount、poolSize、completedTaskCount 这几个指标。
如果非核心线程一直在创建销毁,那就调大 keepAliveTime。
如果非核心线程很少用到,调小一点也没事。
最后说一句:
keepAliveTime 不是一个孤立参数。它跟 corePoolSize、maximumPoolSize、workQueue 是联动的。
调这个参数时,最好一眼扫过去,看看线程池的整体水位。