线程池面试必问:从 Executors 到 ThreadPoolExecutor,这篇够了

5 阅读6分钟

线程池面试必问:从 Executors 到 ThreadPoolExecutor,这篇够了

—— 那个让你崩溃的 ThreadPoolExecutor 7 个参数,今天彻底搞懂。

你在 Java 面试中被问过线程池吗?面试官冷不丁一句:"讲讲 ThreadPoolExecutor 的 7 个参数。" 你瞬间脑补 —— corePoolSize、maximumPoolSize... 一串名词在脑海中打转,但就是说不清楚它们到底在干什么。

别慌,今天我们不背概念,我们讲故事

一、ThreadPoolExecutor 的 7 个核心参数:一家公司的运营模型

想象你在经营一家公司,线程池就是你的员工管理系统。7 个参数,就是这家公司的运营规则。

java

public ThreadPoolExecutor(
    int corePoolSize,              // 1. 核心线程数(正式员工)
    int maximumPoolSize,            // 2. 最大线程数(正式+临时工上限)
    long keepAliveTime,             // 3. 临时工空闲多久辞退
    TimeUnit unit,                  // 4. 时间单位
    BlockingQueue<Runnable> workQueue,  // 5. 任务队列(等候区)
    ThreadFactory threadFactory,    // 6. 线程工厂(人事部门)
    RejectedExecutionHandler handler   // 7. 拒绝策略(任务爆满怎么处理)
);

参数拆解,带类比:

  1. corePoolSize(核心线程数) —— 正式员工

    • 无论任务多少,这些人永远在岗,即使没事做也不会被裁员。
    • 类比:公司的正式编制人员。
  2. maximumPoolSize(最大线程数) —— 员工上限

    • 正式工 + 临时工的总和,公司能承载的最大人力。
    • 超过这个数,新任务就只能走拒绝流程。
  3. keepAliveTime(空闲存活时间) —— 临时工的保质期

    • 临时工闲着超过这个时间,就被辞退。
    • 核心线程默认不辞退,除非你显式设置 allowCoreThreadTimeOut(true)
  4. workQueue(任务队列) —— 等候区

    • 任务太多时,正式工忙不过来,任务先在这里排队。
    • 常用:LinkedBlockingQueue(无界)、ArrayBlockingQueue(有界)、SynchronousQueue(不存,直接交接)。
  5. ThreadFactory(线程工厂) —— 人事部

    • 统一给线程起名、设置优先级、处理异常。
    • 默认工厂生成名字如 pool-1-thread-1,生产环境强烈建议自定义,方便日志排查。
  6. RejectedExecutionHandler(拒绝策略) —— 任务爆满后的处理

    • 队列满 + 线程满,新任务怎么办?
    • 四种内置策略:AbortPolicy(抛异常)、CallerRunsPolicy(调用者自己跑)、DiscardPolicy(直接丢)、DiscardOldestPolicy(丢最老的)。

二、Executors 四种工具类:封装背后的陷阱

Executors 是个便捷工厂,帮你快速创建常用线程池。但 —— 阿里规范明确禁止生产环境直接使用它。为什么?我们一个个拆解。

1. newFixedThreadPool —— 固定线程池

java

ExecutorService pool = Executors.newFixedThreadPool(5);

底层参数:

  • corePoolSize = 5
  • maximumPoolSize = 5
  • workQueue = LinkedBlockingQueue(无界)

特点:

  • 线程数固定,不会扩容。
  • 任务队列无界,理论可以无限排队。

适用场景: 稳定的后台任务、请求量可预测的服务。

风险: 无界队列可能导致任务堆积 OOM。

2. newSingleThreadExecutor —— 单线程池

java

ExecutorService pool = Executors.newSingleThreadExecutor();

底层参数:

  • corePoolSize = 1
  • maximumPoolSize = 1
  • workQueue = LinkedBlockingQueue(无界)

特点:

  • 串行执行,绝对无并发冲突。
  • 线程崩溃会自动重建。

适用场景: 需要顺序执行的任务(日志写入、消息顺序处理)。

风险: 同样是无界队列,有 OOM 隐患。

3. newCachedThreadPool —— 缓存线程池

java

ExecutorService pool = Executors.newCachedThreadPool();

底层参数:

  • corePoolSize = 0
  • maximumPoolSize = Integer.MAX_VALUE
  • keepAliveTime = 60秒
  • workQueue = SynchronousQueue(不存储)

特点:

  • 来任务就创建线程,上限极大。
  • 空闲 60 秒自动回收线程。

适用场景: 大量短生命周期任务、高并发突发请求。

风险: 线程数可能爆炸,耗尽系统资源。

4. newScheduledThreadPool —— 定时任务线程池

java

ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);

底层参数:

  • corePoolSize = 你传入的数
  • maximumPoolSize = Integer.MAX_VALUE
  • workQueue = DelayedWorkQueue(延迟队列)

特点:

  • 支持延迟执行、周期执行。
  • 继承自 ThreadPoolExecutor,参数稍有不同。

适用场景: 定时任务、心跳检测、周期性数据同步。

风险: 最大线程数无限,高并发下同样有风险。

三、一张表看懂四种线程池

表格

线程池corePoolSizemaximumPoolSize队列类型特点
Fixed固定值固定值LinkedBlockingQueue(无界)线程数固定,不扩容
Single11LinkedBlockingQueue(无界)单线程串行
Cached0无限大SynchronousQueue自动扩容+回收
Scheduled固定值无限大DelayedWorkQueue支持定时/周期任务

四、线程池执行任务的完整流程(面试必问)

所有线程池都严格遵循这个 5 步流程

  1. 任务进来 → 核心线程未满 → 创建核心线程执行
  2. 核心线程已满 → 任务进入等待队列排队
  3. 队列已满 → 创建非核心线程执行
  4. 总线程数 = 最大线程数 → 触发拒绝策略
  5. 任务执行完 → 非核心线程空闲超时 → 自动销毁

记住这个流程,面试官再问你"线程池怎么处理任务的",你就可以从容画图解释了。

五、饱和策略:任务满了怎么办?

当队列满 + 线程满,线程池会触发饱和策略。四种内置策略:

  1. AbortPolicy(默认) —— 抛异常,阻止程序继续

    • 生产不推荐,会直接导致任务丢失 + 程序崩溃
  2. CallerRunsPolicy(最实用) —— 调用者自己执行

    • 谁提交任务,谁自己跑(让主线程执行)
    • 不会丢任务,能自动降低提交速度,生产环境首选!
  3. DiscardPolicy —— 直接丢任务,不抛异常

    • 生产绝对禁止!无法排查问题
  4. DiscardOldestPolicy —— 丢掉队列最老的任务,再尝试提交新任务

    • 生产极少用,可能导致关键任务丢失

六、ThreadFactory:必须自定义线程名

默认工厂生成的线程名如 pool-1-thread-1,生产环境无法区分业务,排查问题极难。

推荐写法:

java

ThreadFactory factory = new ThreadFactory() {
    private final AtomicInteger num = new AtomicInteger(1);
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        thread.setName("订单-处理线程-" + num.getAndIncrement());
        thread.setDaemon(true); // 可选:设置为守护线程
        return thread;
    }
};

这样在日志中看到线程名,立刻就能定位是哪个业务模块出问题。

七、企业级最佳实践(必背)

  1. 不用 Executors,手动 new ThreadPoolExecutor

  2. 用有界队列 ArrayBlockingQueue,避免 OOM

  3. 拒绝策略用 CallerRunsPolicy,安全不丢任务

  4. 必须自定义 ThreadFactory,线程名带业务名称

  5. 核心线程数配置建议

    • CPU 密集型:核心数 = CPU 核心数
    • IO 密集型:核心数 = 2 × CPU 核心数

总结

  • 所有线程池底层都是 ThreadPoolExecutor,核心是 7 个参数
  • 四种工具类只是封装,生产环境慎用
  • 核心逻辑:任务 → 核心线程 → 队列 → 非核心线程 → 拒绝策略
  • 线程池设计的终极目标:稳定、可控、不宕机、好排查

下次面试官再问线程池,你可以自信地说:"线程池就像公司员工管理,核心参数就是正式工、临时工、等候区、拒绝策略的运营规则 —— 我从源码到最佳实践都懂。"

希望这篇文章能帮你真正理解线程池,而不是死记硬背。有问题欢迎交流,我们下期再见。