Skip to content

线程池学习总览 ​

线程池不是“让代码异步一下”的工具,而是 Java 后端系统控制并发、保护资源、隔离故障的核心组件。接口突然变慢、请求堆积、CPU 飙高、内存上涨、数据库连接池打满、MQ 消费延迟,很多线上问题最后都能追到线程池配置和使用方式。

本目录把线程池从多线程中独立出来,按原理、参数、执行流程、队列、拒绝策略、关闭、监控和排查完整学习。

学习目标 ​

学完线程池专题,要能回答:

  1. 为什么不能每个任务都 new Thread()。
  2. Executor、ExecutorService、ThreadPoolExecutor 有什么关系。
  3. 七大核心参数分别控制什么。
  4. 线程池为什么是“核心线程 -> 队列 -> 最大线程 -> 拒绝策略”的流程。
  5. 不同阻塞队列会如何改变线程池行为。
  6. 为什么生产不推荐直接使用 Executors。
  7. CPU 密集型、IO 密集型、下游受限任务如何估算参数。
  8. 线程池打满、任务堆积、线程饥饿死锁如何排查。

线程池解决什么问题 ​

如果每来一个任务都创建一个线程:

mermaid
flowchart TD
    A["请求到来"] --> B["new Thread"]
    B --> C["执行业务任务"]
    C --> D["线程结束销毁"]
    A --> E["高并发时大量创建线程"]
    E --> F["上下文切换增加"]
    E --> G["线程栈占用内存"]
    E --> H["下游数据库或接口被打爆"]

短期看代码简单,长期会出现三个问题:

问题原因后果
创建销毁成本高线程是操作系统资源,不是普通对象高并发时 CPU 浪费在调度上
数量不可控请求越多线程越多内存上涨,甚至无法创建新线程
缺少排队和拒绝系统没有容量边界无限堆积直到 OOM 或整体雪崩

线程池的本质是:用有限线程处理大量任务,用队列吸收短期峰值,用拒绝策略在系统过载时保护自己。

线程池核心结构 ​

mermaid
flowchart TD
    A["业务线程提交任务"] --> B["ThreadPoolExecutor"]
    B --> C["Worker 工作线程集合"]
    B --> D["BlockingQueue 任务队列"]
    B --> E["RejectedExecutionHandler 拒绝策略"]
    B --> F["ThreadFactory 线程工厂"]
    C --> G["执行 Runnable 或 Callable"]
    D --> C

核心组件:

组件作用
ThreadPoolExecutor线程池核心实现,控制线程创建、队列、拒绝、关闭
Worker线程池内部工作线程,循环取任务执行
BlockingQueue保存等待执行的任务
ThreadFactory创建线程并设置名称、是否守护线程、异常处理
RejectedExecutionHandler线程池满了以后决定任务怎么处理

学习顺序 ​

mermaid
flowchart TD
    A["线程池为什么存在"] --> B["七大核心参数"]
    B --> C["execute 执行流程"]
    C --> D["阻塞队列和拒绝策略"]
    D --> E["参数估算和线程池隔离"]
    E --> F["关闭流程和任务异常"]
    F --> G["监控指标和线上排查"]

推荐按下面顺序学习:

  1. ExecutorService:已有基础页,理解接口和基本使用。
  2. 核心参数:把七大参数逐个讲透。
  3. 执行流程原理:理解任务提交后到底怎么流转。
  4. 队列与拒绝策略:理解队列选择如何改变线程池行为。
  5. 参数估算与监控:理解商业项目怎么配、怎么观测。
  6. 源码级生命周期与线上排查:把 execute、Worker、getTask、线程状态、队列堆积和 jstack 排查串起来。
  7. 关闭流程与常见坑:理解优雅停机、异常吞掉、饥饿死锁。
  8. Callable、Future与FutureTask:理解有返回值任务和异常获取。

最小可运行 Demo ​

java
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolQuickStart {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,
                4,
                30,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<Runnable>(10),
                runnable -> {
                    Thread thread = new Thread(runnable);
                    thread.setName("order-worker-" + thread.getId());
                    return thread;
                },
                new ThreadPoolExecutor.CallerRunsPolicy()
        );

        for (int i = 0; i < 20; i++) {
            final int taskId = i;
            pool.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " 处理订单任务 " + taskId);
            });
        }

        pool.shutdown();
    }
}

这个 Demo 有几个重点:

  1. 使用有界队列 ArrayBlockingQueue,避免任务无限堆积。
  2. 自定义线程名,线上排查 jstack 时能看出线程用途。
  3. 使用 CallerRunsPolicy,线程池满了让提交线程自己执行,形成反压。
  4. 最后调用 shutdown(),避免应用无法退出。

为什么不推荐直接用 Executors ​

Executors 写起来方便,但很多工厂方法隐藏了风险。

方法隐藏风险
newFixedThreadPool默认使用无界 LinkedBlockingQueue,任务可能无限堆积
newSingleThreadExecutor也是无界队列,单线程卡住会导致后续任务全部等待
newCachedThreadPool使用 SynchronousQueue,最大线程数接近无限,流量高时线程数失控
newScheduledThreadPool周期任务异常可能停止后续执行,需要捕获异常

生产环境更推荐显式创建 ThreadPoolExecutor,把线程数、队列长度、线程名、拒绝策略都写清楚。

商业常用场景 ​

场景线程池作用配置重点
接口聚合并行调用多个下游接口超时、隔离、最大并发
批量采集控制采集并发,避免打爆目标系统队列长度、限流、重试
异步入库削峰,把写入任务排队处理有界队列、失败补偿
MQ 消费控制消费速度和业务处理并发幂等、重试、消费堆积
报表导出避免大任务占满业务线程独立线程池、任务状态

面试标准回答 ​

问题:线程池有什么作用?

标准回答:

线程池用于复用线程、控制并发数量、管理任务队列和拒绝策略。它避免每个任务都创建新线程带来的资源浪费,也能在流量高峰时通过队列缓冲任务,在系统过载时通过拒绝策略保护服务。生产中线程池必须结合业务类型、下游容量、队列长度、超时和监控来配置,不能无脑使用 Executors。

本章小结 ​

线程池是并发控制工具,不是无限加速工具。理解线程池要抓住四件事:线程数量有限、队列容量有限、下游容量有限、过载时必须有拒绝或反压。后续章节会逐步拆解参数、执行流程、队列、拒绝策略、生命周期、监控和排查。