在高并发Java开发和微服务应用中,线程池(Thread Pool)已成为提升性能、资源复用、控制并发的重要基石。线程池可以避免频繁创建/销毁线程的高昂开销,让任务调度、线程生命周期、拒绝策略都能有序可控。理解线程池中的任务处理完整流程,是搞懂Java并发和调优实战的前提。本文将详细梳理线程池从任务提交到最终执行/拒绝的每一环,结合源码和运维建议,带你彻底掌握线程池的核心机制。
一、为什么需要线程池?
- 线程创建/销毁昂贵:普通new Thread每次都创建新线程,系统开销巨大。
- 资源可控:线程数固定或受限,防止服务线程数爆炸引发资源枯竭。
- 任务管理和解耦:生产者-消费者模型,让任务和调度彻底分离,便于代码解耦。
- 功能丰富:支持超时/定时/队列/拒绝策略/扩容收缩等。
- 系统健壮性:降低内存泄露、死锁、本地线程数打满的风险。
二、线程池的核心组件
Java线程池底层由ThreadPoolExecutor(java.util.concurrent包)实现,主要包括:
- 核心线程数(corePoolSize)
- 最大线程数(maximumPoolSize)
- 任务队列(workQueue)
- 线程工厂(threadFactory)
- 拒绝策略(RejectedExecutionHandler)
- 活动线程集合(workers)
三、线程池任务处理完整流程
1. 任务提交
开发者通过executor.execute(Runnable)或submit(Callable)提交一个任务对象到线程池。
2. 主体流程——任务“接收-排队-调度-执行”
以ThreadPoolExecutor为例,详细分为下列步骤:
Step1:判断当前活动线程数
- 如果当前“活动线程数”小于
corePoolSize:- 新建线程直接执行新任务(即使有空闲线程)。
- (核心线程只要没满都直接创建)
Step2:核心线程满,尝试进队列
- 如果活动线程数>=
corePoolSize,任务尝试进入任务队列workQueue(如LinkedBlockingQueue)。 - 若队列未满,任务被排队等待,由池中空闲线程取走执行。
Step3:队列满,尝试扩容到最大线程
- 如果任务队列已满,但线程数仍小于
maximumPoolSize,则创建新线程执行新任务。
Step4:最大线程也已满,任务进入拒绝策略
- 如果活动线程=最大线程,且队列也满,任务无法处理,根据线程池拒绝策略决定如何应对(常见策略有Abort/Discard/CallerRuns等)。
Step5:任务实际执行
- 线程池中的某个工作线程worker获取到任务后,调用线程的run方法执行任务run()。
- 若任务为FutureTask,内部还可处理返回值/异常。
Step6:线程存活治理
- 非核心线程空闲超时时间到
keepAliveTime后会被销毁回收。 - 线程池优先保持核心线程,非核心线程用于高峰补充。
四、线程池任务典型源码流程图解
+---------------------+
| 任务 submit/execute|
+----------+----------+
|
+---------------v----------------+
| 活动线程 < corePoolSize ? |
| 是 -> 创建worker直接执行任务 |
+----+--------------------------+
|
| 否
v
+----------+-----------+
| 队列workQueue 是否满?|
| 否 -> 进队列排队 |
+----------+-----------+
|
| 是
v
+--------------+-------------+
| 活动线程 < maxPoolSize ? |
| 是 -> 创建worker执行任务 |
+--------------+-------------+
| 否
v
+-----------+-------------+
| 执行拒绝策略 (Abort等) |
+-------------------------+
五、实际代码演示
1. 线程池基础用法案例
import java.util.concurrent.*;
public class ThreadPoolFlowDemo {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, // corePoolSize
4, // maximumPoolSize
10, // keepAliveTime
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2), // workQueue
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
for (int i = 1; i <= 10; i++) {
final int taskNum = i;
pool.execute(() -> {
System.out.println("任务" + taskNum + "由线程" + Thread.currentThread().getName() + "执行");
try { Thread.sleep(2000);} catch (InterruptedException ignored) {}
});
}
pool.shutdown();
}
}
2. 线程池处理任务节点说明
- 前2个任务 -> 分别由2个核心线程直接执行
- 第3、第4个任务 -> 队列能容纳2个,进队列排队
- 第5、第6个任务 -> 核心+队列都满,最大线程池还能扩容2个,创建非核心线程执行
- 第7及后续任务 -> 线程数满+队列满,任务根据拒绝策略处理(上例直接抛异常)
六、线程池的拒绝策略
常见的拒绝策略(均实现RejectedExecutionHandler接口):
| 策略 | 行为描述 |
|---|---|
| AbortPolicy | 直接抛出RejectedExecutionException(默认) |
| CallerRunsPolicy | 由提交任务的当前线程自己执行 |
| DiscardPolicy | 直接丢弃当前任务,不做任何处理 |
| DiscardOldestPolicy | 丢弃队列头的最老一个任务,然后再次尝试提交 |
生产环境推荐监控拒绝计数,并合理扩展/告警。
七、线程池任务处理流程的优化建议
-
队列类型影响很大
- 有界队列(如ArrayBlockingQueue):防止OOM,适合高可靠服务
- 无界队列(如LinkedBlockingQueue):允许海量任务入列,但易堆积导致延迟
-
核心/最大线程需结合CPU/内存评估
- CPU密集型:线程数 = CPU核数+1
- IO密集型:线程数远大于CPU核数,具体视业务测试
-
拒绝策略要搭配监控和预警机制
- 防止任务无声丢失或异常压制
-
线程池应全局化/单例复用
- 切勿频繁new ThreadPoolExecutor
-
销毁时注意shutdown和awaitTermination配合
- 防止业务丢失
八、高级运用:定时/异步/批量任务
- 定时用
ScheduledThreadPoolExecutor - 批量用
invokeAll/invokeAny方法组合Future/Callable - 异步非阻塞可配合
CompletableFuture等
170万+

被折叠的 条评论
为什么被折叠?



