线程池通过复用线程、减少创建销毁开销、控制并发数来提升吞吐量;需据任务类型合理设核心线程数、使用有界队列和恰当拒绝策略,避免OOM或上下文切换反噬性能。

线程池为什么能提升并发性能
直接说结论:线程池通过复用已创建的线程、减少频繁创建/销毁开销、控制并发数防止资源耗尽,来提升实际吞吐量。不是“用了就快”,而是“用对了才快”。
常见误解是以为线程越多越快——实际上当 ThreadPoolExecutor 的核心线程数远超 CPU 核心数(比如 100 个线程跑纯计算任务),上下文切换开销会反噬性能,响应时间反而飙升。
- IO 密集型任务(如 HTTP 调用、DB 查询)可适当放大线程数,常用公式:
corePoolSize ≈ CPU核心数 × (1 + 平均等待时间 / 平均工作时间) - CPU 密集型任务建议设为
Runtime.getRuntime().availableProcessors()或 ±1 - 拒绝策略不能全用
AbortPolicy:高并发下直接抛RejectedExecutionException可能导致上游雪崩,考虑CallerRunsPolicy让调用线程自己执行,起到自然降速作用
execute() 和 submit() 的行为差异必须分清
这两个方法看似都能提交任务,但返回值、异常处理、执行路径完全不同,选错会导致问题难以定位。
-
execute(Runnable):无返回值,任务中抛出的异常会直接打印到stderr(除非自定义ThreadFactory设置了UncaughtExceptionHandler) -
submit(Runnable)或submit(Callable):返回Future,异常被封装进Future.get(),不调用get()就永远看不到失败 - 若任务逻辑可能抛异常且需要感知,必须用
submit+ 显式future.get()(注意加超时,避免永久阻塞)
keepAliveTime 对非核心线程的实际影响常被低估
这个参数只对「超出 corePoolSize 的空闲线程」生效。很多人以为设了 60 秒,所有线程都会在空闲后 60 秒回收——其实核心线程默认永不回收(除非设置 allowCoreThreadTimeOut(true))。
立即学习“Java免费学习笔记(深入)”;
- 默认情况下,即使队列为空、无任务,
corePoolSize以内的线程一直存活 - 若希望轻量级保活(比如夜间流量低时自动缩容),需显式调用
setAllowCoreThreadTimeOut(true) -
keepAliveTime单位容易写错:TimeUnit.SECONDS和TimeUnit.MILLISECONDS混用会导致线程过早或过晚回收
使用 Executors 工具类创建线程池的风险点
Executors.newFixedThreadPool、newCachedThreadPool 看似方便,但在生产环境极易引发故障。
-
newFixedThreadPool(n)底层用的是无界LinkedBlockingQueue:任务持续涌入时,队列无限增长,最终 OOM -
newCachedThreadPool()允许创建 Integer.MAX_VALUE 个线程:突发流量下线程数暴增,直接打满系统内存和句柄数 - 正确做法:始终用
ThreadPoolExecutor构造器手动指定corePoolSize、maximumPoolSize、有界队列(如ArrayBlockingQueue)、拒绝策略
真正难的不是配置几个数字,而是理解你的任务类型、平均响应时间、峰值 QPS 和系统资源之间的约束关系。一个没设界的队列,可能让服务在压测时表现良好,上线后某次促销就直接挂掉。











