线程池

ℹ️本篇定位

并发面试出镜率最高的一篇。主线三件事:为什么要用线程池(复用降低开销、统一管理防资源耗尽)、七个核心参数 + 提交执行流程(必考画图)、为什么禁用 Executors 快捷方法(阿里规约高频题)。参数怎么配、常见队列有哪些,文末给速查。

为什么需要线程池

  • 降低资源消耗:线程的创建和销毁都涉及系统调用与内存分配,复用线程摊薄成本;
  • 提高响应速度:任务到达直接复用已有线程,省去创建等待;
  • 统一管理:防止无限制 new Thread 把系统线程数打爆,还能提供排队、监控、超时等治理能力。

标准创建:ThreadPoolExecutor 七参数

ExecutorService pool = new ThreadPoolExecutor(
        2,                               // corePoolSize   核心线程数:常驻,不回收(默认)
        4,                               // maximumPoolSize 最大线程数:核心+临时上限
        60, TimeUnit.SECONDS,            // keepAliveTime + unit 临时线程空闲多久回收
        new LinkedBlockingQueue<>(100),  // workQueue      任务排队队列
        Executors.defaultThreadFactory(),// threadFactory  建线程的工厂(自定义名字方便排查)
        new ThreadPoolExecutor.AbortPolicy());  // handler  队列满+线程满时的拒绝策略

提交任务后的执行流程(必考画图题)

提交任务
  │
  ▼
核心线程未满?──是──▶ 创建核心线程执行
  │ 否
  ▼
队列没满?────是──▶ 任务入队等待
  │ 否
  ▼
最大线程未满?──是──▶ 创建临时线程执行
  │ 否
  ▼
执行拒绝策略

注意顺序坑:是先入队,队列满了才开临时线程,不是先开满线程再排队——很多人栽在这里。

四种拒绝策略

策略 行为
AbortPolicy(默认) RejectedExecutionException
CallerRunsPolicy 谁提交谁自己执行——天然反压,常用于业务池
DiscardPolicy 静默丢弃新任务
DiscardOldestPolicy 丢掉队首最老任务,给新任务腾位

为什么禁用 Executors 的快捷方法(阿里规约)

// ❌ 都是坑
Executors.newFixedThreadPool(10);  // 队列是无界 LinkedBlockingQueue —— 任务堆积直接 OOM
Executors.newSingleThreadExecutor(); // 同上,无界队列
Executors.newCachedThreadPool();   // 最大线程数是 Integer.MAX_VALUE —— 线程无限增长 OOM
// ✅ 手动 new ThreadPoolExecutor,明确每个参数

参数怎么设(经验值 + 原理)

  • CPU 密集型(计算、加解密):核心线程数 ≈ N_CPU + 1
  • IO 密集型(RPC 调用、数据库、文件):核心线程数 ≈ N_CPU × (1 + 等待时间/计算时间),粗略按 2N 起步压测调整;
  • 队列:有界!容量按可容忍的积压量定,并配好监控;
  • 更科学的做法:压测 + 监控(活跃线程数、队列水位、拒绝次数),参数不是拍出来的。

submit vs execute / 优雅关闭

pool.execute(runnable);          // 无返回值,异常直接打印堆栈
Future<String> f = pool.submit(callable); // 有返回值,异常在 get() 时抛出

pool.shutdown();                 // 不接新任务,已提交的跑完
pool.awaitTermination(30, TimeUnit.SECONDS); // 等待收尾
pool.shutdownNow();              // 中断正在执行的任务(慎用)

:submit 的任务里抛了异常但你不调 get(),异常会被吞掉——日志里什么都看不到。处理:任务内部 try-catch,或包一层异常记录。

其他要点速记

  • 核心线程默认不回收,allowCoreThreadTimeOut(true) 可让核心线程也走 keepAlive 回收;
  • 线程池的线程默认都是非守护线程,主线程退出不影响它们,忘记 shutdown 会导致 JVM 不退出;
  • 动态调参:setCorePoolSize() 运行时可改(美团的动态线程池实践就是基于此 + 监控)。

⬅️ 02-synchronized与volatile 🏠 00-Java ➡️ 04-Lock与AQS