Skip to content

线程池学习总览

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

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

学习目标

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

  1. 为什么不能每个任务都 new Thread()
  2. ExecutorExecutorServiceThreadPoolExecutor 有什么关系。
  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. 源码级生命周期与线上排查:把 executeWorkergetTask、线程状态、队列堆积和 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

本章小结

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