```html
欢迎来到 KaoAcca.cn - 专注Java并发编程深度解析

线程池面试题讲解

在Java高级开发面试中,线程池是绝对的高频考点。它不仅考察你对JUC包的理解,更考察你对系统资源管理、并发安全及架构设计的综合能力。本页面将为您提供一份详尽的线程池面试题讲解指南,涵盖从基础参数到源码级别的深度剖析。

一、 线程池核心:ThreadPoolExecutor 七大参数

面试官最常问的第一题通常是:“请描述一下 ThreadPoolExecutor 的构造参数”。这是理解线程池工作机制的基石。在 线程池面试题讲解 中,这一部分必须烂熟于心。

1. 核心线程数 (corePoolSize)

线程池中长期存活的线程数量,即使它们是空闲的,也不会被销毁(除非设置了 allowCoreThreadTimeOut)。当有新任务提交时,如果当前线程数小于 corePoolSize,则会创建新线程执行任务。

2. 最大线程数 (maximumPoolSize)

线程池允许创建的最大线程数。只有当队列满了,且当前线程数小于 maximumPoolSize 时,才会创建新线程。注意:如果使用的是无界队列,maximumPoolSize 将失效。

3. 非核心线程存活时间 (keepAliveTime)

当线程数大于 corePoolSize 时,空闲线程的最大存活时间。超过这个时间,多余的空闲线程将被销毁,直到线程数回落到 corePoolSize。

4. 时间单位 (unit)

keepAliveTime 的时间单位,如 TimeUnit.SECONDS。

5. 工作队列 (workQueue)

用于保存等待执行的任务的阻塞队列。常见的有:

  • ⚡ ArrayBlockingQueue:有界队列,基于数组的先进先出队列。
  • ⚡ LinkedBlockingQueue:无界队列(默认容量为Integer.MAX_VALUE),基于链表的先进先出队列。
  • ⚡ SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作。
  • ⚡ PriorityBlockingQueue:具有优先级的无限阻塞队列。

6. 线程工厂 (threadFactory)

用于创建新线程的工厂。默认情况下,线程由 Executors.defaultThreadFactory() 创建,线程名为 "pool-1-thread-1"。自定义线程工厂可以为线程设置更有意义的名字,便于排查问题。

7. 拒绝策略 (handler)

当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这就是 拒绝策略 的作用。

拒绝策略 处理方式 适用场景
AbortPolicy 直接抛出 RejectedExecutionException 异常 默认策略,适用于对数据一致性要求高的场景
CallerRunsPolicy 由调用线程(提交任务的线程)处理该任务 降级处理,减缓任务提交速度,不丢失任务
DiscardPolicy 直接丢弃任务,不抛异常 适用于对任务不敏感、可丢失的场景
DiscardOldestPolicy 丢弃队列中最老的任务,尝试重新提交当前任务 适用于实时性要求高,可牺牲旧任务的场景

二、 线程池运行机制深度解析

理解线程池如何分配任务是面试中的难点。我们使用选项卡来展示不同的任务提交场景。

场景一:任务少,空闲多
场景二:任务激增
场景三:系统饱和

核心线程优先创建

当提交一个新任务时,如果当前线程数 < corePoolSize,线程池会创建一个新的核心线程来执行任务,即使此时其他核心线程是空闲的。这是为了应对突发流量,保证核心处理能力。

// 伪代码逻辑
if (workerCount < corePoolSize) {
    createCoreThread();
} else {
    // 进入队列
}

队列满后创建非核心线程

如果当前线程数 >= corePoolSize,新任务会被放入 workQueue 中。如果队列是有界的且满了,线程池会尝试创建非核心线程(只要当前线程数 < maximumPoolSize)。

else if (!workQueue.offer(task)) {
    if (workerCount < maximumPoolSize) {
        createNonCoreThread();
    } else {
        // 触发拒绝策略
        reject(task);
    }
}

拒绝策略触发

当线程数达到 maximumPoolSize 且队列也满了时,线程池无法再处理新任务,此时将调用 RejectedExecutionHandler 的 rejectedExecution 方法。

这是系统过载的信号,通常伴随着日志报警或降级逻辑。

三、 高频面试题:如何正确创建线程池?

在 线程池面试题讲解 中,Executors 工具类的使用是一个巨大的陷阱。面试官会问:“Executors.newFixedThreadPool 有什么隐患?”

1. 阿里开发手册禁止使用的线程池创建方式

阿里巴巴 Java 开发手册明确规定:线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式。这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

2. 隐患分析

  • ⚠️ newFixedThreadPool / newSingleThreadExecutor:允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量请求,导致 OOM (OutOfMemoryError)。
  • ⚠️ newCachedThreadPool:允许创建的线程数量为 Integer.MAX_VALUE,可能创建大量线程,导致 OOM。

3. 正确的创建姿势

手动创建 ThreadPoolExecutor,并根据业务类型合理设置参数:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, // corePoolSize
    10, // maximumPoolSize
    60L, // keepAliveTime
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(100), // 有界队列
    new ThreadPoolExecutor.CallerRunsPolicy() // 安全拒绝策略
);

4. 核心线程数设置指南

CPU 密集型任务

此类任务计算量大,线程上下文切换开销相对较小。建议设置线程数为 CPU 核心数 + 1,即 N + 1。这样可以最大限度地减少线程切换,提高 CPU 利用率。

IO 密集型任务

此类任务大部分时间在等待 IO 操作(如数据库查询、HTTP 请求),CPU 利用率低。建议设置线程数为 CPU 核心数 2,或者根据并发量适当增加,如 2N 或 N / (1 - 阻塞系数)。

混合型任务

可以根据任务的性质拆分,或者通过监控线程池状态动态调整参数。

四、 进阶:线程池源码中的关键方法

对于高级开发岗位,面试官可能会深入询问源码。以下是 execute() 方法的核心逻辑梳理。

execute() 方法执行流程

  1. 如果当前运行的线程少于 corePoolSize,则创建新线程来执行任务。
  2. 如果运行的线程等于或多于 corePoolSize,则将任务加入 workQueue。
  3. 如果无法将任务加入 workQueue(队列已满),则检查当前运行的线程数是否少于 maximumPoolSize。
  4. 如果少于,则创建新线程执行任务。
  5. 如果多于,则执行拒绝策略。

shutdown() vs shutdownNow()

  • ⚙️ shutdown():启动有序关闭,先前提交的任务会执行,但新任务不会被接受。
  • ⚙️ shutdownNow():尝试停止所有正在执行的任务,暂停等待处理的任务,并返回等待执行的任务列表。

注意:shutdownNow() 并不能保证立即停止正在执行的任务,它只是发送中断信号 (interrupt())。任务内部需要响应中断才能真正停止。

六、 总结

通过本页面关于 线程池面试题讲解 的详细梳理,相信您已经对 Java 线程池有了全面的认识。从七大参数的配置,到运行流程的把控,再到拒绝策略的选择和源码级的理解,每一个环节都是面试中的得分点。同时,结合实际业务场景选择合适的线程池配置,避免 OOM 和线程泄漏,是区分初级与高级开发者的关键。

希望这份攻略能帮助您在面试中脱颖而出,顺利拿到心仪的 Offer!

```
斌知网
蜀ICP备2026017620号-5