线程池复用工作线程,控制并发资源。面试重点是任务先交给谁、什么时候扩容或拒绝,以及怎样避免任务无边界堆积。
七个核心参数
| 参数 | 作用 |
|---|---|
| corePoolSize | 核心线程数,通常按需创建,也可预启动 |
| maximumPoolSize | 最大线程数 |
| keepAliveTime | 空闲线程保活时间 |
| unit | 保活时间单位 |
| workQueue | 等待任务的队列 |
| threadFactory | 线程创建、命名等 |
| handler | 任务无法接收时的拒绝策略 |
默认主要回收超过核心数量的空闲线程,启用 allowCoreThreadTimeOut 后核心线程也可超时回收。
execute 的基本流程
提交任务 ├─ 线程数不足 core:尝试创建线程直接执行 └─ 否则尝试入队 ├─ 入队成功:由工作线程获取任务,并复查池状态 └─ 入队失败:尝试创建线程,直到 maximum └─ 无法创建或池已关闭:拒绝通常先到核心数,再入队,队列满了才扩展到最大数。 不是先把线程开到最大才排队。关闭竞态等状态检查属于实际实现必要步骤,基础面试不必逐行背。
无界队列持续接收任务时,maximumPoolSize 通常难以起到扩容作用,积压可能耗尽内存。SynchronousQueue 不保存普通排队任务,而是尝试直接交接。
四种常见拒绝策略
- AbortPolicy:抛 RejectedExecutionException,默认策略。
- CallerRunsPolicy:池未关闭时由提交线程执行,形成反馈;提交方是请求线程时会增加请求延迟。
- DiscardPolicy:丢弃任务。
- DiscardOldestPolicy:通常丢弃队列头部任务,再尝试提交,有丢失任务和队列语义限制。
必须处理的业务不能静默丢弃。CallerRunsPolicy 也不是无损队列,池关闭时不会继续执行被拒绝任务。
Executors 常见工厂有什么风险?
这些工厂方便创建线程池,但要知道默认边界,不是所有场景都禁止使用 Executors。
| 工厂 | 常见实现特点 | 主要风险 |
|---|---|---|
| newFixedThreadPool(n) | 固定线程数,使用无界 LinkedBlockingQueue | 生产快于消费时,任务持续堆积并占用内存 |
| newSingleThreadExecutor() | 单工作线程,无界任务队列 | 慢任务阻塞后续任务,积压没有合理的业务上限 |
| newCachedThreadPool() | SynchronousQueue 直接交接,最大线程数很大 | 任务长时间占用线程时,可能不断创建线程耗尽资源 |
无界队列不代表无限内存,最大线程数很大也不代表系统能承受。固定池的问题主要是排队增长,Cached 池的问题主要是线程增长,两者不要混淆。
需要明确资源预算的服务通常显式构造 ThreadPoolExecutor:限制队列和最大并发,配上合适拒绝策略、线程命名与监控。其他池有自己的模型,不能把这张表套到所有 Executors 工厂。
怎样配置线程数?
CPU 密集任务以可用 CPU 核数附近为起点;阻塞 IO 任务可能用更多线程,但必须考虑数据库连接数、下游容量、响应时间和内存。没有脱离工作负载的最佳公式。
ExecutorService pool = new ThreadPoolExecutor( 4, 8, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());这是参数关系示例,不是线上通用配置。实际应命名线程,监控活跃线程、排队量、耗时和拒绝次数,通过压测调整,必要时按任务类型隔离线程池。
execute、submit 与关闭
execute 提交 Runnable,没有返回 Future;submit 返回 Future。submit 的任务异常通常由 Future 保存,调用 get 时通过 ExecutionException 暴露,不读取结果可能漏掉失败。
shutdown 停止接收新任务,继续处理已接收任务;shutdownNow 尝试中断工作线程,返回尚未开始的排队任务,不保证运行中的任务一定停止。任务还需要正确响应取消和超时。
面试回答模板
线程池的七个参数包括核心数、最大数、保活时间与单位、队列、线程工厂和拒绝策略。通常先创建核心线程,再排队,队列满后扩展到最大数,仍无法处理就拒绝。配置要结合任务类型和下游容量,限制积压并监控拒绝,不能靠无界队列和大最大线程数解决所有问题。
自测
- 核心线程正在忙、队列还没满时,通常先排队还是开非核心线程?先排队。
- 无界队列配大 maximum 能解决积压吗?不能。
- submit 后不读取 Future 有什么风险?任务异常可能被忽略。
- Fixed 和 Cached 池的主要资源风险一样吗?不一样,前者主要是任务积压,后者可能是线程数量增长。