线程池(Thread Pool)是一种基于池化思想管理线程 的并发工具,核心目标是复用线程资源、控制并发数量、降低资源开销 。其执行流程涉及任务提交、线程调度、任务排队、拒绝策略等多个环节,是Java并发编程中最常用的多线程管理方案(如ThreadPoolExecutor
)。
一、线程池的核心组件
在理解执行流程前,需先明确线程池的核心组件及其作用:
组件 | 说明 |
---|---|
核心线程数(corePoolSize) | 线程池长期保留的最小线程数(即使空闲也不会被销毁,除非设置allowCoreThreadTimeOut )。 |
最大线程数(maximumPoolSize) | 线程池允许创建的最大线程数(核心线程+非核心线程的总和)。 |
任务队列(workQueue) | 当核心线程全忙时,新任务暂时存入的队列(如ArrayBlockingQueue 、LinkedBlockingQueue )。 |
非核心线程存活时间(keepAliveTime) | 非核心线程(超出核心线程数的线程)空闲多久后被回收的时间阈值。 |
拒绝策略(RejectedExecutionHandler) | 当任务无法被核心线程、队列、非核心线程处理时,执行的回调策略(如丢弃任务、抛异常)。 |
线程工厂(ThreadFactory) | 用于创建线程的工厂类(可自定义线程名称、优先级等)。 |
二、线程池执行流程:从任务提交到执行
线程池的执行流程可分为5个阶段 ,核心逻辑是"优先复用核心线程→队列缓冲→创建非核心线程→触发拒绝策略"。以下是详细步骤:
阶段1:提交任务(submit/execute)
用户通过execute(Runnable task)
或submit(Callable task)
提交任务到线程池。线程池接收到任务后,立即进入调度逻辑。
阶段2:核心线程是否空闲?
线程池首先检查核心线程是否都在忙碌:
- 若有空闲的核心线程:直接由空闲核心线程执行任务(无需入队)。
- 若核心线程全忙:进入阶段3(任务入队)。
阶段3:任务入队(workQueue.offer())
当核心线程全忙时,线程池会将新任务尝试存入任务队列:
- 队列未满:任务成功入队,等待核心线程空闲后从队列头部取出执行。
- 队列已满:进入阶段4(创建非核心线程)。
阶段4:创建非核心线程(workerCount < maximumPoolSize)
若任务队列已满,线程池会检查当前线程总数(核心线程+非核心线程)是否小于maximumPoolSize
:
- 是 :创建非核心线程(临时线程)执行该任务(非核心线程空闲超时后会被回收)。
- 否:进入阶段5(触发拒绝策略)。
阶段5:触发拒绝策略(RejectedExecutionHandler)
当以下3个条件同时满足时,触发拒绝策略:
- 核心线程全忙;
- 任务队列已满;
- 线程总数已达
maximumPoolSize
(无法创建新线程)。
此时线程池会调用RejectedExecutionHandler
处理任务,常见策略有4种:
策略 | 行为 |
---|---|
AbortPolicy(默认) | 直接抛出RejectedExecutionException 异常,拒绝任务。 |
CallerRunsPolicy | 由调用者(提交任务的线程)直接执行该任务("自己干")。 |
DiscardPolicy | 静默丢弃任务,不抛异常也不执行。 |
DiscardOldestPolicy | 丢弃队列中最旧的任务(队头任务),然后将新任务重新入队(尝试再次调度)。 |
关键补充:核心线程的超时回收
若设置了allowCoreThreadTimeOut(true)
,核心线程在空闲超时(keepAliveTime
)后也会被回收,此时线程池可能降级到0线程(仅保留任务队列中的任务)。
三、线程池架构图(ASCII版)
以下是线程池执行流程的架构图,展示了组件间的关系和任务流动路径:
markdown
+-------------------+ +------------------+ +------------------+ +------------------+
| 提交任务 | -------> | 核心线程池 | -------> | 任务队列 | -------> | 非核心线程池 |
| (execute/submit) | | (corePoolSize) | | (workQueue) | | (maxPoolSize-core)|
+-------------------+ +------------------+ +------------------+ +------------------+
↑ ↓ ↓ ↓
|(核心线程空闲) |(核心线程忙,队列未满) |(队列满,线程数未达上限) |(线程数达上限) |
| | | |
| v v v
| +------------------+ +------------------+ +------------------+
| | 核心线程执行任务 | | 任务入队等待 | | 创建非核心线程 |
| | (Worker.run()) | | (Queue.offer()) | | (new Worker()) |
| +------------------+ +------------------+ +------------------+
| |
| v
| +------------------+
| | 触发拒绝策略 |
| | (RejectedHandler) |
| +------------------+
↓ |
+-------------------+ +------------------+
| 任务完成/异常 | <----------------------------------------------------------------------- | 任务执行/丢弃 |
| (Future.get()) | +------------------+
+-------------------+
### 图解说明:
1. **任务提交**:用户通过`execute`提交任务,线程池开始调度。
2. **核心线程执行**:若有空闲核心线程,直接执行任务(`Worker`线程通过`run()`方法处理任务)。
3. **任务入队**:核心线程忙时,任务入队(如`ArrayBlockingQueue`),等待核心线程空闲后消费。
4. **非核心线程创建**:队列满且线程数未达上限时,创建非核心线程执行任务。
5. **拒绝策略**:队列满且线程数达上限时,触发拒绝策略处理任务。
## 四、执行流程示例(以`ThreadPoolExecutor`为例)
假设线程池配置:`corePoolSize=2`,`maximumPoolSize=5`,`workQueue=ArrayBlockingQueue(3)`,`rejectedPolicy=AbortPolicy`。
1. **提交前2个任务**:核心线程(2个)空闲,直接执行。
2. **提交第3-5个任务**:核心线程忙,任务依次入队(队列容量3,此时队列满)。
3. **提交第6个任务**:核心线程忙+队列满,创建非核心线程(第3个线程)执行。
4. **提交第7-9个任务**:继续创建非核心线程(第4、5个线程)执行。
5. **提交第10个任务**:核心线程(2)+非核心线程(3)=5(达`maximumPoolSize`),队列已满(3),触发`AbortPolicy`,抛出异常。
## 五、总结
线程池的执行流程本质是**"资源分级利用"**:
- 核心线程:长期驻留,减少线程创建开销;
- 任务队列:缓冲突发流量,避免频繁创建线程;
- 非核心线程:应对短期流量高峰,空闲后回收;
- 拒绝策略:防止资源耗尽,保护系统稳定性。
理解这一流程,能帮助开发者合理配置线程池参数(如`corePoolSize`、`workQueue`类型),避免"线程爆炸"或"任务堆积"问题,是高并发系统设计的必备技能。