面试题考点分析:
- 核心线程数(corePoolSize)与最大线程数(maximumPoolSize)的设定逻辑。
- 阻塞队列(如 LinkedBlockingQueue、SynchronousQueue)的选择与容量规划。
- 拒绝策略(RejectedExecutionHandler)的触发条件及四种内置策略的适用场景。
- 线程存活时间(keepAliveTime)的作用及超出核心线程的回收机制。
- 线程池参数之间的协作关系,以及如何根据任务类型(CPU 密集型 / IO 密集型)进行参数调优。
一、标准回答
合理设置线程池参数的核心是根据任务类型、系统资源和业务容忍度进行动态配平。以下为通用推荐值和决策公式:
| corePoolSize | 核心线程数 | Ncpu + 1 | Ncpu * 2 或 Ncpu / (1 – 阻塞系数) |
| maximumPoolSize | 最大线程数 | 与核心一致或略大 | 根据 IO 需求和队列容量设定,通常为 Ncpu * (1 + 平均 IO 耗时/CPU 耗时) |
| keepAliveTime | 空闲线程存活时间 | 0(一般不回收核心线程) | 较长(如 60s),用于回收超出核心的线程 |
| workQueue | 任务队列 | SynchronousQueue 或容量较小的有界队列 | 容量较大的有界队列(LinkedBlockingQueue),防止无限堆积 |
| rejectedExecutionHandler | 拒绝策略 | CallerRunsPolicy 以保证任务不丢失 | 根据业务选择:丢弃并记录(DiscardPolicy)、抛异常(AbortPolicy)或由调用线程执行 |
其中 Ncpu 为可用处理器数,阻塞系数 = 线程等待 IO 的时间占比,可通过监控估算。实际项目中,建议先在压测环境验证,再结合运行时动态调整。
二、核心原理
ThreadPoolExecutor 的生命周期如下:
官方文档明确指出:“There are three general strategies for queuing: Direct handoffs, Unbounded queues, Bounded queues.” 即同步移交(SynchronousQueue)、无界队列和有限队列。选择不同队列直接决定任务处理的优先级和资源消耗。
当线程池状态变为 STOP 或 TERMINATED 时,正在执行的任务可能被中断,已提交但未执行的任务会被丢弃,因此必须通过 shutdown() 或 shutdownNow() 优雅关闭。
三、应用场景
3.1 日常应用场景
- Web 请求异步处理:将耗时的 IO 操作(如调用第三方接口)放入线程池,避免阻塞主线程。
- 批量任务处理:大数据量的导入导出、日志处理、消息消费等,使用线程池并发处理以提升效率。
- 定时任务:结合 ScheduledThreadPoolExecutor 执行周期性任务。
3.2 主流技术框架中的线程池配置
1. Spring Boot(@Async + ThreadPoolTaskExecutor)
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors());
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);
executor.setQueueCapacity(500);
executor.setKeepAliveSeconds(60);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
2. Tomcat 线程池(Connector 线程)
在 application.yml 中调整:
server:
tomcat:
threads:
max: 200 # 最大工作线程数
min-spare: 10 # 最小空闲线程数
accept-count: 100 # 等待队列长度
3. Netty(EventLoop 线程池)
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(
Runtime.getRuntime().availableProcessors() * 2
);
4. Dubbo(服务提供者与消费者线程池)
<dubbo:protocol name="dubbo" threads="200" queues="0" />
Dubbo 默认使用固定 200 线程,队列长度为 0(同步移交),可通过 threads 和 queues 进行调整。
四、使用方式
4.1 手动创建线程池(推荐)
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) {
int cpuCores = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
cpuCores, // 核心线程数
cpuCores * 2, // 最大线程数
60L, TimeUnit.SECONDS, // 空闲线程存活时间
new LinkedBlockingQueue<>(500), // 有界队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
// 提交任务
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "执行任务");
try { Thread.sleep(1000); } catch (InterruptedException e) { }
});
}
// 优雅关闭
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
}
4.2 动态调整参数
executor.setCorePoolSize(20); // 动态修改核心线程数
executor.setMaximumPoolSize(40); // 动态修改最大线程数
executor.setKeepAliveTime(30, TimeUnit.SECONDS);
executor.allowCoreThreadTimeOut(true);// 允许核心线程超时回收
4.3 监控与指标
通过 getPoolSize()、getActiveCount()、getQueue().size() 等 API 配合定时任务输出线程池状态,或接入 Micrometer、Actuator 进行监控告警。
System.out.printf("池中线程数:%d,活跃线程:%d,队列任务数:%d,已完成任务:%d%n",
executor.getPoolSize(),
executor.getActiveCount(),
executor.getQueue().size(),
executor.getCompletedTaskCount());
五、扩展延伸
- 动态线程池框架:如 Dynamic-Tp(基于 Spring 的可动态刷新线程池参数),支持配置中心推送,无需重启。
- 拒绝策略自定义:实现 RejectedExecutionHandler,将拒绝的任务记录到数据库或 MQ 中,保证不丢失。
- 线程池隔离:不同业务使用独立线程池,防止相互影响,例如 Hystrix 的线程隔离策略。
- 避免线程池死锁:如果任务之间存在相互依赖(如同一个线程池内提交的子任务再等待父任务),容易引发线程饥饿,此时应使用不同的线程池或 ForkJoinPool。
- 合理命名线程:使用 ThreadFactory 自定义线程名称,方便日志排查。
六、面试追问
Q1:核心线程数和最大线程数可以相等吗?什么场景下相等?
可以相等。当希望线程池大小固定时(如 CPU 密集型任务),通常设置 corePoolSize == maximumPoolSize,配合无界队列(不推荐)或小容量队列使用,避免无限制扩容。
Q2:队列满了会发生什么?
如果队列已满且运行线程数尚未达到最大线程数,会创建新线程处理任务;若已达最大线程数,则执行拒绝策略。默认的 AbortPolicy 会抛出 RejectedExecutionException,因此必须根据业务选择合适的策略。
Q3:如何优雅关闭线程池?
shutdown() 不再接受新任务,但会执行完已提交的任务;shutdownNow() 尝试中断正在执行的任务并返回未执行的任务列表。推荐的关闭步骤如第四节代码所示:先 shutdown,再 awaitTermination,超时后调用 shutdownNow。
Q4:使用 Executors 工具类创建的线程池有什么问题?
FixedThreadPool 和 SingleThreadPool 使用无界队列 (LinkedBlockingQueue),可能导致 OOM;CachedThreadPool 的最大线程数为 Integer.MAX_VALUE,也可能引起 OOM。因此 阿里巴巴开发手册强制要求手动创建 ThreadPoolExecutor。
Q5:线程池的线程数是不是越多越好?
不是。线程切换会消耗 CPU 资源,线程过多会增加调度开销和内存占用。需要根据任务类型和系统瓶颈(CPU/IO)确定合适的上限,并通过压测验证。



