这是一份非常详细、实用且通俗易懂的线程池权威指南。我们将从背景到实战,由浅入深地为您解读线程池的方方面面。
目录
- 1.1 单线程时代的困境
- 1.2 多线程的曙光与挑战
- 1.3 线程池的诞生:解决问题的关键
- 2.1 核心组件剖析
- 2.2 关键参数详解
- 2.3 线程的生命周期与状态管理
- 3.1 任务提交流程详解
- 3.2 核心线程创建与管理
- 3.3 任务队列策略
- 3.4 非核心线程的创建与回收
- 3.5 拒绝策略
- 4.1 ThreadPoolExecutor 核心源码分析
- 4.2 Worker 类:线程的载体
- 4.3 状态控制 (ctl 变量)
- 4.4 源码关键方法解读 (execute, addWorker, runWorker)
- 5.1 调优目标与原则
- 5.2 核心参数调优实战
- 5.2.1 corePoolSize 怎么设?
- 5.2.2 maximumPoolSize 怎么设?
- 5.2.3 workQueue 选型与容量
- 5.2.4 keepAliveTime 设置
- 5.2.5 threadFactory 定制
- 5.2.6 rejectedExecutionHandler 选择
- 5.3 监控指标:发现问题
- 5.3.1 常用监控指标
- 5.3.2 监控工具使用
- 6.1 JDK ThreadPoolExecutor (基础、灵活)
- 6.2 Executors 工厂类 (快捷但有陷阱)
- 6.3 Spring ThreadPoolTaskExecutor (Spring整合友好)
- 6.4 Apache Commons Pool (对象池,类比学习)
- 6.5 Netty EventLoopGroup (NIO模型专用)
- 6.6 对比总结表
- 7.1 计算密集型任务
- 7.2 I/O密集型任务
- 7.3 混合型任务
- 7.4 异步处理 (消息队列消费、邮件发送等)
- 7.5 定时/周期任务 (配合ScheduledThreadPoolExecutor)
- 7.6 Web服务器请求处理 (Tomcat连接器)
- 8.1 配置自定义线程池 (ThreadPoolTaskExecutor)
- 8.2 使用 @Async 注解实现异步方法
- 8.3 异步方法的事务问题 (@Transactional 传播行为)
- 8.4 线程池的优雅关闭 (shutdown 和 awaitTermination)
- 8.5 实战示例代码
1. 线程池的前世今生:为什么需要它?
1.1 单线程时代的困境 想象一下只有一个收银员的超市。顾客(任务)只能排成一队,一个一个结账。效率低下,顾客等待时间长,收银员忙不过来。这就是单线程处理任务的模型。程序一次只能做一件事。
1.2 多线程的曙光与挑战 为了提高效率,超市雇用了多个收银员(创建多个线程)。顾客可以分散到不同的队伍(线程)结账,效率提升了。然而,问题也随之而来:
- 创建/销毁线程成本高: 每次有顾客来都新雇一个收银员(创建线程),顾客走了就解雇(销毁线程)。招聘、培训、解雇过程(操作系统分配资源、调度线程)很耗时。
- 资源耗尽风险: 顾客太多(并发请求高),收银员无限增加(无限制创建线程),超市(服务器)可能因人手(内存、CPU)不足而崩溃。
- 管理混乱: 缺乏统一管理,收银员(线程)之间协调困难,可能出现争抢资源(并发问题)或无人接待顾客(线程闲置)。
1.3 线程池的诞生:解决问题的关键 线程池就像一个高效的收银员管理中心:
- 预先储备: 提前招聘并培训好一批核心收银员(核心线程),随时待命。
- 灵活调配: 高峰期时,允许临时招聘少量非核心收银员(非核心线程),高峰期过后解雇(回收线程)。
- 任务队列: 当所有收银员都忙时,新来的顾客(任务)在等候区(任务队列)排队。
- 拒绝策略: 当等候区也满了,新来的顾客会被礼貌劝离(拒绝任务)。
- 统一管理: 中心统一管理所有收银员的状态、工作分配,避免混乱。
核心价值: 通过复用线程、限制线程数量、管理任务队列,线程池显著降低了线程创建销毁的开销,提高了系统响应速度和稳定性,并防止资源耗尽。它是构建高性能、高并发应用的基石。
2. 线程池的核心概念
2.1 核心组件剖析 一个线程池主要由以下几个部分组成:
- 核心线程 (corePoolSize): 池中始终保持存活的线程数量(即使它们空闲)。它们是线程池的“常备军”。
- 最大线程数 (maximumPoolSize): 池中允许存在的最大线程数量。这是线程池的“兵力上限”。
- 任务队列 (workQueue): 用于存放待执行任务的阻塞队列。当核心线程都在忙时,新任务会进入队列排队。常见的队列类型有:
- LinkedBlockingQueue:基于链表的无界队列(除非指定容量)。可能导致 OOM。
- ArrayBlockingQueue:基于数组的有界队列。
- SynchronousQueue:不存储元素的队列,每个插入操作必须等待一个移除操作。通常配合较大的 maximumPoolSize 使用。
- PriorityBlockingQueue:具有优先级的无界队列。
- 线程工厂 (threadFactory): 用于创建新线程的工厂。可以定制线程的名称、优先级、是否为守护线程等。
- 拒绝策略 (rejectedExecutionHandler): 当线程池已关闭,或者线程池饱和(核心线程忙、队列满、非核心线程达到最大数)时,对新提交任务的处理策略。JDK 提供了几种默认策略:
- AbortPolicy(默认):抛出 RejectedExecutionException 异常。
- CallerRunsPolicy:在提交任务的线程中直接执行该任务。
- DiscardPolicy:静默丢弃新任务。
- DiscardOldestPolicy:丢弃队列中最老的任务(队首),然后尝试重新提交新任务。
- 非核心线程存活时间 (keepAliveTime): 当线程池中线程数量超过 corePoolSize 时,多余的空闲线程在终止前等待新任务的最长时间。
2.2 关键参数详解
- corePoolSize: 核心线程数。即使空闲也保留,除非设置了 allowCoreThreadTimeOut 为 true。设置太小可能导致频繁创建非核心线程;设置太大会浪费资源。
- maximumPoolSize: 最大线程数。限制线程池的膨胀,防止资源耗尽。必须 >= corePoolSize。
- workQueue: 缓冲队列。选择有界还是无界,直接影响系统的抗压能力和稳定性。
- keepAliveTime: 控制非核心线程的闲置时间。单位通常为 TimeUnit(秒、毫秒等)。
- threadFactory: 用于定制线程行为,便于监控和调试(如设置线程名)。
- rejectedExecutionHandler: 定义饱和时的行为,根据业务容忍度选择。定制策略也很常见。
2.3 线程的生命周期与状态管理 线程池内部线程(Worker)的状态主要受任务队列和池状态影响:
- RUNNING: 正常状态,接收新任务并处理队列任务。
- SHUTDOWN: 不再接收新任务,但会处理完队列中的任务。
- STOP: 不再接收新任务,也不再处理队列任务,并尝试中断正在执行的任务。
- TIDYING: 所有任务已终止,workerCount 为 0。线程池将调用 terminated() 钩子方法。
- TERMINATED: terminated() 方法执行完毕。
线程(Worker)本身:
- 当无任务可执行(队列空且无新任务)且是非核心线程(或核心线程但允许超时)时,在 keepAliveTime 后会被回收销毁。
- 核心线程(默认)会一直存活等待任务。
3. 线程池的工作原理(算法)
线程池处理任务的核心流程可以用一个清晰的步骤来描述:
任务提交 (execute(Runnable command) 或 submit)
判断当前线程数 (workerCount)
- 如果 workerCount < corePoolSize:
- 尝试创建新的核心线程 (addWorker(command, true)) 来执行这个任务。成功则结束流程。
- 创建失败(可能由于线程工厂异常或池状态变化),进入下一步。
- 如果 workerCount >= corePoolSize 或上一步创建核心线程失败:
- 尝试将任务 command.offer() 加入任务队列 (workQueue)。
- 如果入队成功:
- 再次检查池状态(因为入队过程中状态可能变化)。
- 如果池处于 RUNNING 状态,且 workerCount == 0(可能所有核心线程都刚好结束),则创建一个非核心线程 (addWorker(null, false)) 来处理队列任务(确保有线程在工作)。
- 入队成功且后续检查正常,流程结束(任务在队列等待)。
- 如果入队失败(队列已满):
- 尝试 创建新的非核心线程 (addWorker(command, false)) 来执行这个任务。
- 如果创建非核心线程成功,流程结束。
- 如果创建非核心线程失败(达到 maximumPoolSize 或池已关闭),则 执行拒绝策略 (reject(command))。
核心/非核心线程执行任务 (runWorker(Worker w))
- 线程(Worker)启动后,循环执行以下操作:
- 从任务队列 task = workQueue.poll() 或 take() 获取任务(取决于超时设置和线程类型)。
- 如果获取到任务 (task != null),则执行该任务 (task.run())。
- 执行完任务后,继续尝试获取下一个任务。
非核心线程回收
- 非核心线程在尝试从队列获取任务时,如果使用 poll(keepAliveTime, unit) 且超时(keepAliveTime 时间内没拿到新任务),则该线程将被终止并从池中移除。
流程图简化表示: 任务提交 -> [核心线程有空? 是: 立即执行] -> [否, 队列未满? 是: 入队等待] -> [否, 还能创建非核心线程? 是: 创


