线程池是复用与管控Thread对象的机制,解决频繁创建/销毁开销及资源耗尽问题;应避免Executors工厂方法,改用显式配置的ThreadPoolExecutor,合理设置核心线程数、有界队列、拒绝策略,并务必手动shutdown。

线程池不是“用完就扔”的线程集合,而是对 Thread 对象的复用与管控机制——它解决的核心问题是:频繁创建/销毁线程带来的开销,以及无节制并发导致的资源耗尽。
为什么不能每次 new Thread().start()?
直接新建线程看似简单,但隐藏三个硬伤:
- 每次
new Thread()都触发 JVM 分配栈内存(默认 1MB)、注册线程到 OS、调度初始化,开销远高于普通对象 - 线程数失控时,
OutOfMemoryError: unable to create new native thread会突然出现,而非业务逻辑报错 - 缺乏统一生命周期管理:无法等待任务完成、无法优雅关闭、无法监控活跃数或队列积压
Java 标准线程池怎么选?看 Executors 工厂方法的陷阱
Executors 提供的静态方法看似方便,但多数在生产环境不推荐:
-
Executors.newFixedThreadPool(n)→ 底层用LinkedBlockingQueue(无界),任务持续提交会导致 OOM(队列无限增长) -
Executors.newCachedThreadPool()→ 核心线程数 0,最大线程数Integer.MAX_VALUE,突发流量可能瞬间创建数千线程,打爆 CPU 和内存 -
Executors.newSingleThreadExecutor()→ 单线程,适合串行化任务,但队列仍是无界的,同样有 OOM 风险
正确做法是绕过 Executors,直接构造 ThreadPoolExecutor,显式控制:核心线程数、最大线程数、空闲存活时间、任务队列(建议用有界 ArrayBlockingQueue)、拒绝策略。
立即学习“Java免费学习笔记(深入)”;
拒绝策略不是摆设:RejectedExecutionHandler 怎么用?
当线程池已满(线程数达上限 + 队列已满)时,新任务会被拒绝。JDK 提供四种内置策略,但默认的 AbortPolicy(抛 RejectedExecutionException)往往不合适:
-
CallerRunsPolicy:让提交任务的线程自己执行该任务 → 降低提交速度,适合保护下游系统 -
DiscardPolicy:静默丢弃 → 适用于日志等可丢失场景 -
DiscardOldestPolicy:丢弃队列头任务,再尝试提交 → 可能影响任务顺序,慎用 - 自定义策略更常见:记录告警、降级为异步写磁盘、转投消息队列
例如:
new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy());
线程池必须手动 shutdown 吗?
是的。JVM 不会自动回收运行中的线程池,即使主线程结束,只要池中还有非守护线程(默认都是),JVM 就不会退出。
- 调用
shutdown():停止接收新任务,等待已有任务执行完 - 调用
shutdownNow():尝试中断所有正在执行的任务,并返回未执行的任务列表 - 务必配合
awaitTermination()等待终止,否则可能提前退出导致任务丢失
常见漏点:Spring 中用 @Bean 声明的线程池,需实现 DisposableBean 或用 @PreDestroy 注解确保关闭;没被容器管理的池,得在应用关闭钩子(Runtime.getRuntime().addShutdownHook())里处理。
线程池配置没有银弹,核心参数(核心线程数、队列容量、拒绝策略)必须结合实际业务的 QPS、平均响应时间、错误容忍度来压测调整;很多人卡在“照着博客配了却线上崩了”,问题往往出在队列类型选错,或者忘了关。









