Java线程池的创建与管理

应手动构建 ThreadPoolExecutor,显式设置核心参数:使用 ArrayBlockingQueue 限定队列容量(如1024),拒绝策略选 CallerRunsPolicy,自定义命名线程工厂,并合理调用 shutdown() 与 awaitTermination() 完成优雅关闭。

为什么不能直接 new ThreadPoolExecutor?

直接调用 ThreadPoolExecutor 构造函数容易漏掉关键参数组合

,比如拒绝策略、队列容量、线程工厂——这些一旦设错,线上可能突然拒绝任务或 OOM。JDK 提供的 Executors 工具类封装了常见模式,但其中 Executors.newFixedThreadPool()Executors.newCachedThreadPool() 有隐患:newFixedThreadPool() 用的是无界 LinkedBlockingQueue,任务堆积会吃光堆内存;newCachedThreadPool() 允许创建无限线程,突发流量下可能耗尽 OS 线程资源。

如何安全地创建一个可监控的固定线程池?

推荐手动构建 ThreadPoolExecutor,显式控制所有核心参数,并替换默认的无界队列和拒绝策略:

  • ArrayBlockingQueue 替代 LinkedBlockingQueue,限定队列长度(如 1024)
  • 拒绝策略选 ThreadPoolExecutor.CallerRunsPolicy(让调用线程自己执行任务,自然限流)或自定义策略记录日志+告警
  • 设置 threadFactory,用 ThreadFactoryBuilder(Guava)或手动实现,给线程命名,便于排查
  • 开启 allowCoreThreadTimeOut(true)(可选),让空闲核心线程也能回收
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    4,                          // corePoolSize
    8,                          // maxPoolSize
    60L,                        // keepAliveTime
    TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(1024),
    new ThreadFactoryBuilder().setNameFormat("biz-task-%d").build(),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

submit() 和 execute() 到底该用哪个?

execute(Runnable) 只接受无返回值任务,失败时异常会直接抛到线程的 UncaughtExceptionHandler,若未设置,就默默吞掉;submit() 返回 Future,能捕获执行异常(调用 get() 时抛出 ExecutionException 包裹原始异常)。实际业务中:

  • 异步发通知、写日志等“火种型”任务,用 execute() 更轻量
  • 需要结果、超时控制或错误重试的任务,必须用 submit() 并显式处理 Future.get(3, TimeUnit.SECONDS)
  • 永远不要忽略 Future 的返回值——不调 get() 就无法感知任务是否失败

线程池 shutdown 的正确姿势是什么?

简单调 shutdown() 不等于立刻停掉所有线程:它只是停止接收新任务,已提交任务仍会执行完。真正关闭需两步:

  • 先调 shutdown(),停止接收
  • 再调 awaitTermination(long, TimeUnit) 等待完成,超时后调 shutdownNow() 尝试中断正在运行的任务(注意:仅对阻塞在 sleep/wait/join 或可响应中断的 I/O 的线程有效)
  • 务必检查 awaitTermination() 返回值,为 false 说明有任务没结束,这时要考虑是否强制终止或记录告警

Spring 环境下建议用 @PreDestroy 方法触发关闭逻辑,避免 JVM 退出时线程池残留。

线程池不是“创建完就完事”的组件,队列类型、拒绝策略、线程命名、关闭流程,每个点都可能在线上引发隐蔽问题。尤其要注意:无界队列 + 大量慢任务 = 内存泄漏;不处理 Future 异常 = 故障静默;忘记 awaitTermination = 应用假死。