Administrator
发布于 2026-04-25 / 2 阅读
0

面试官:Java 线程池的核心参数是什么?线程池是怎么工作的?

面试官:Java 线程池的核心参数是什么?线程池是怎么工作的?

线程池是 Java 并发编程的核心组件,几乎所有 Java 后端项目都在用它。但很多人只会调 Executors.newFixedThreadPool(),对内部原理一知半解。本文带你彻底搞懂线程池。


一、为什么需要线程池?

直接 new Thread() 有三个核心问题:

  1. 频繁创建/销毁开销大:线程的创建涉及内核态切换,每次 new Thread 代价不菲
  2. 线程数量不可控:无限制创建线程会耗尽内存,导致 OOM
  3. 缺乏统一管理:线程的生命周期、异常处理分散,难以监控

线程池通过复用线程限制并发数量统一管理生命周期解决了这三个问题。


二、ThreadPoolExecutor 的七个核心参数

Java 线程池的真正构造函数是 ThreadPoolExecutor,接收七个参数:

public ThreadPoolExecutor(
    int corePoolSize,          // 核心线程数
    int maximumPoolSize,       // 最大线程数
    long keepAliveTime,        // 空闲线程存活时间
    TimeUnit unit,             // 时间单位
    BlockingQueue<Runnable> workQueue,    // 任务队列
    ThreadFactory threadFactory,          // 线程工厂
    RejectedExecutionHandler handler      // 拒绝策略
)

参数一:corePoolSize(核心线程数)

线程池常驻的线程数量。即使线程空闲,也不会被销毁(除非设置了 allowCoreThreadTimeOut(true))。

参数二:maximumPoolSize(最大线程数)

线程池允许创建的最大线程数。当任务队列满了之后,才会创建超过 corePoolSize 的线程,但总数不超过 maximumPoolSize。

参数三 & 四:keepAliveTime + unit(空闲线程存活时间)

超出 corePoolSize 的临时线程,如果空闲时间超过这个值就会被销毁。核心线程不受此影响(默认情况下)。

参数五:workQueue(任务队列)

当核心线程全部繁忙时,新任务会先进入任务队列等待。常用的几种:

队列类型特点适用场景
LinkedBlockingQueue无界队列(默认容量 Integer.MAX_VALUE)newFixedThreadPool 使用,但有 OOM 风险
ArrayBlockingQueue有界队列,需指定容量推荐用于生产环境,可控
SynchronousQueue不存储任务,来一个立即交给线程newCachedThreadPool 使用
PriorityBlockingQueue按优先级排序的无界队列需要任务优先级时使用

参数六:threadFactory(线程工厂)

用于自定义线程的创建方式,常见用途是给线程命名,方便排查问题:

ThreadFactory namedFactory = new ThreadFactoryBuilder()
    .setNameFormat("order-pool-%d")
    .build();

参数七:handler(拒绝策略)

当任务队列满 + 线程数达到 maximumPoolSize 时,新任务被拒绝,触发拒绝策略:

策略行为
AbortPolicy(默认)直接抛出 RejectedExecutionException
CallerRunsPolicy调用者线程(提交任务的线程)直接执行该任务
DiscardPolicy静默丢弃任务,不报错
DiscardOldestPolicy丢弃队列中最老的任务,然后尝试重新提交当前任务

三、线程池的工作流程

这是面试最爱问的核心逻辑,务必掌握:

提交任务
    ↓
当前运行线程数 < corePoolSize?
    ├── YES → 创建核心线程执行任务 ✅
    └── NO ↓
任务队列未满?
    ├── YES → 任务入队等待 ✅
    └── NO ↓
当前运行线程数 < maximumPoolSize?
    ├── YES → 创建非核心线程执行任务 ✅
    └── NO → 触发拒绝策略 ❌

用一句话总结:先填满核心线程 → 再塞满队列 → 再扩展到最大线程 → 最后才拒绝。

常见误区:很多人以为"任务来了先检查队列",实际上是先创建核心线程,核心线程满了才入队,队满了才创建非核心线程


四、线程池的状态机

ThreadPoolExecutor 内部用一个 AtomicInteger 的高 3 位存储线程池状态,低 29 位存储线程数量:

状态说明
RUNNING正常运行,接受新任务,处理队列任务
SHUTDOWN调用 shutdown() 后,不接受新任务,但处理完队列剩余任务
STOP调用 shutdownNow() 后,不接受新任务,中断正在执行的任务,清空队列
TIDYING所有任务已终止,线程数为 0,即将调用 terminated()
TERMINATEDterminated() 执行完毕

五、为什么不推荐使用 Executors 工厂方法?

《阿里巴巴 Java 开发手册》明确规定:禁止使用 Executors 创建线程池,原因如下:

// ❌ 危险!使用无界队列,任务堆积会 OOM
ExecutorService fixed = Executors.newFixedThreadPool(10);
// 底层:new LinkedBlockingQueue<Runnable>(),容量 Integer.MAX_VALUE

// ❌ 危险!线程数无上限,大量请求会创建大量线程,OOM
ExecutorService cached = Executors.newCachedThreadPool();
// 底层:maximumPoolSize = Integer.MAX_VALUE

正确做法:直接使用 ThreadPoolExecutor,明确指定所有参数:

// ✅ 推荐方式
ExecutorService executor = new ThreadPoolExecutor(
    10,                         // corePoolSize
    20,                         // maximumPoolSize
    60L,                        // keepAliveTime
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(500),   // 有界队列,容量 500
    new ThreadFactoryBuilder().setNameFormat("biz-pool-%d").build(),
    new ThreadPoolExecutor.CallerRunsPolicy()   // 调用者执行,反压上游
);

六、线程池参数如何合理设置?

经典公式(仅供参考,实际需根据业务测试调优):

CPU 密集型任务(计算、加密、压缩等):

corePoolSize = CPU核心数 + 1

线程过多反而增加上下文切换开销,+1 是为了在偶尔的内存缺页等情况下保持 CPU 不空闲。

IO 密集型任务(数据库、网络请求等):

corePoolSize = CPU核心数 × (1 + 等待时间/计算时间)

IO 期间线程阻塞,可以有更多线程填满 CPU。如果等待时间是计算时间的 4 倍,则 corePoolSize = CPU核心数 × 5

实际生产中建议:先设保守值,用压测工具(如 JMeter)不断调整,并开启线程池监控(暴露 Metrics)。


七、常见面试追问

Q:线程池里的线程是如何复用的?

Worker 线程执行完一个任务后,不会立即销毁,而是循环调用 getTask() 从队列里拉取下一个任务。这个 while 循环就是线程复用的关键:

// Worker.run() 的简化逻辑
while (task != null || (task = getTask()) != null) {
    task.run();
}

Q:shutdown() 和 shutdownNow() 的区别?

  • shutdown():温和关闭。不再接受新任务,等待队列中的任务执行完毕后关闭。
  • shutdownNow():强制关闭。中断所有线程(发送 interrupt),返回未执行的任务列表。

Q:如何监控线程池的运行状态?

ThreadPoolExecutor executor = ...;
executor.getPoolSize();          // 当前线程数
executor.getActiveCount();       // 活跃线程数(正在执行任务的)
executor.getQueue().size();      // 队列中等待的任务数
executor.getCompletedTaskCount(); // 已完成任务数

小结

参数作用注意事项
corePoolSize常驻线程数根据任务类型合理设置
maximumPoolSize线程上限必须 ≥ corePoolSize
workQueue缓冲任务生产环境用有界队列
handler拒绝策略CallerRunsPolicy 可实现反压

掌握了线程池的工作原理,不仅能在面试中游刃有余,在生产中排查线程池满、任务堆积等问题时也能快速定位根因。