Java 线程池详解:核心参数、执行流程、拒绝策略、实现原理与调优
线程池是 Java 并发编程中非常重要的组件,主要用于解决线程频繁创建和销毁带来的资源浪费,以及线程数量不可控带来的系统风险。
线程池的核心作用可以概括为:
复用线程、控制并发、管理任务、保护系统资源
本文主要总结 Java 线程池的核心参数、执行流程、常见类型、拒绝策略、状态变化、实现原理以及调优思路。
一、什么是线程池
线程池就是提前创建并维护一组线程,当任务提交时,由线程池中的线程执行任务。任务执行完成后,线程不会立即销毁,而是继续等待执行下一个任务。
简单理解:
线程池 = 工作线程 + 任务队列 + 线程管理机制
没有线程池时,每来一个任务就创建一个线程;有了线程池后,线程可以被复用,从而减少资源消耗。
线程池的核心思想:
1. 线程复用 2. 控制并发数量 3. 任务排队 4. 统一管理线程生命周期
二、为什么要使用线程池
使用线程池主要有以下几个好处:
1. 降低资源消耗
线程的创建和销毁都需要消耗系统资源。线程池可以复用已有线程,减少频繁创建和销毁线程带来的开销。
2. 提高响应速度
线程池中的线程可以提前创建好,任务提交后可以直接执行,不需要等待线程创建。
3. 提高线程可管理性
线程池可以统一管理线程数量、任务队列、拒绝策略以及线程生命周期。
4. 防止资源耗尽
如果每个请求都创建一个线程,高并发下可能导致线程数暴涨,引发 CPU 频繁上下文切换、内存占用过高,甚至 OOM。线程池可以限制最大线程数,保护系统稳定性。
三、线程池核心参数
Java 线程池的核心实现类是 ThreadPoolExecutor,构造方法如下:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
);
主要有 7 个核心参数。
1. corePoolSize:核心线程数
线程池中长期保留的线程数量。
当当前线程数小于 corePoolSize 时,提交任务会优先创建核心线程执行。
2. maximumPoolSize:最大线程数
线程池允许创建的最大线程数量。
当核心线程已满,并且任务队列也满了,才会创建非核心线程,直到达到 maximumPoolSize。
3. keepAliveTime:空闲线程存活时间
非核心线程空闲超过该时间后会被回收。
默认情况下,核心线程不会被回收。如果调用:
executor.allowCoreThreadTimeOut(true);
核心线程也可以超时回收。
4. unit:时间单位
keepAliveTime 的时间单位,例如:
TimeUnit.SECONDS TimeUnit.MILLISECONDS TimeUnit.MINUTES
5. workQueue:任务队列
用于存放等待执行的任务。
常见队列:
| 队列 | 说明 |
|---|---|
ArrayBlockingQueue | 有界阻塞队列 |
LinkedBlockingQueue | 链表阻塞队列 |
SynchronousQueue | 不存储任务 |
PriorityBlockingQueue | 优先级阻塞队列 |
生产环境建议使用有界队列,避免任务无限堆积。
6. threadFactory:线程工厂
用于创建线程。
实际开发中通常会自定义线程名称,方便排查问题,例如:
order-pool-1 payment-pool-1
7. handler:拒绝策略
当线程数达到最大值,并且任务队列也满了,新任务就会触发拒绝策略。
四、线程池执行流程
线程池提交任务后,执行流程可以概括为:
核心线程 → 任务队列 → 非核心线程 → 拒绝策略
具体流程如下:
1. 当前线程数 < corePoolSize 创建核心线程执行任务 2. 当前线程数 >= corePoolSize 尝试将任务放入任务队列 3. 任务队列已满,且当前线程数 < maximumPoolSize 创建非核心线程执行任务 4. 任务队列已满,且当前线程数 >= maximumPoolSize 执行拒绝策略
假设线程池参数如下:
corePoolSize = 2 maximumPoolSize = 4 queueSize = 2
连续提交 7 个任务:
| 任务 | 处理方式 |
|---|---|
| 任务 1 | 创建核心线程执行 |
| 任务 2 | 创建核心线程执行 |
| 任务 3 | 进入队列 |
| 任务 4 | 进入队列 |
| 任务 5 | 创建非核心线程执行 |
| 任务 6 | 创建非核心线程执行 |
| 任务 7 | 触发拒绝策略 |
为什么核心线程满了先进入队列?
因为线程池的设计目标是控制线程数量,减少线程创建和上下文切换成本。
可以这样理解:
corePoolSize:常规处理能力 workQueue:缓冲能力 maximumPoolSize:峰值处理能力
只有当队列也满了,说明当前压力已经超过常规处理能力,才会创建非核心线程。
如果使用 SynchronousQueue,由于它不存储任务,所以核心线程满了后会直接创建非核心线程。
五、线程池常见类型
1. FixedThreadPool
固定大小线程池。
特点:
核心线程数 = 最大线程数 线程数量固定 任务多了进入队列
适合任务量稳定、并发可控的场景。
2. CachedThreadPool
缓存线程池。
特点:
核心线程数为 0 最大线程数非常大 使用 SynchronousQueue 空闲线程会被回收
适合大量短时间异步任务。
风险是高并发下可能创建大量线程,导致资源耗尽。
3. SingleThreadExecutor
单线程线程池。
特点:
只有一个工作线程 任务按提交顺序执行
适合需要保证任务顺序执行的场景,例如异步写日志、顺序处理消息等。
4. ScheduledThreadPool
定时任务线程池。
特点:
支持延迟执行 支持周期性执行
适合定时清理缓存、定时同步数据、心跳检测等场景。
5. WorkStealingPool
工作窃取线程池,底层基于 ForkJoinPool。
适合任务可以拆分的并行计算场景,例如递归任务、大任务拆分等。
6. 自定义 ThreadPoolExecutor
生产环境更推荐使用自定义线程池。
示例:
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10,
20,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);这样可以明确控制核心线程数、最大线程数、队列大小、线程名称和拒绝策略。
六、线程池拒绝策略
当线程池线程数达到最大值,并且任务队列也满了,就会触发拒绝策略。
Java 内置了 4 种拒绝策略。
| 拒绝策略 | 处理方式 | 适合场景 |
|---|---|---|
AbortPolicy | 直接抛异常 | 核心业务,需要感知失败 |
CallerRunsPolicy | 调用线程自己执行 | 任务不能丢,需要削峰 |
DiscardPolicy | 直接丢弃新任务 | 可丢弃的非核心任务 |
DiscardOldestPolicy | 丢弃队列中最老任务 | 只关心最新任务 |
生产环境也可以自定义拒绝策略,例如记录日志、发送告警、写入数据库或 MQ 后续补偿。
RejectedExecutionHandler handler = (task, executor) -> {
System.err.println("任务被拒绝:" + task);
// 记录日志、发送告警、保存任务等
};
七、Executor、ExecutorService、Executors、ThreadPoolExecutor 的区别
1. Executor
最基础的任务执行接口,只有一个方法:
void execute(Runnable command);
2. ExecutorService
继承自 Executor,功能更强,提供了:
submit() shutdown() shutdownNow() invokeAll() invokeAny()
3. Executors
工具类,用于快速创建线程池,例如:
Executors.newFixedThreadPool(10); Executors.newCachedThreadPool(); Executors.newSingleThreadExecutor();
4. ThreadPoolExecutor
线程池的核心实现类,实际开发中推荐直接使用它创建线程池,参数更加可控。
简单总结:
Executor:任务执行接口 ExecutorService:增强版任务执行接口 Executors:线程池工具类 ThreadPoolExecutor:线程池核心实现类
八、线程池状态
ThreadPoolExecutor 主要有 5 种状态:
RUNNING SHUTDOWN STOP TIDYING TERMINATED
| 状态 | 说明 |
|---|---|
RUNNING | 接收新任务,并处理队列任务 |
SHUTDOWN | 不接收新任务,但继续处理已有任务 |
STOP | 不接收新任务,不处理队列任务,并尝试中断正在执行的任务 |
TIDYING | 所有任务结束,工作线程数为 0 |
TERMINATED | 线程池彻底终止 |
常见状态流转:
RUNNING -> SHUTDOWN -> TIDYING -> TERMINATED RUNNING -> STOP -> TIDYING -> TERMINATED
九、线程池实现原理
ThreadPoolExecutor 内部主要依靠以下几个核心组件实现线程管理和任务调度。
1. ctl 变量
线程池内部有一个重要变量:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
ctl 同时保存两个信息:
高 3 位:线程池状态 低 29 位:工作线程数量
这样可以通过一个原子变量同时控制线程池状态和工作线程数量,保证并发安全。
2. Worker 工作线程
线程池中的线程被封装成 Worker。
可以简单理解为:
Worker = 工作线程 + 第一个任务
Worker 中保存了真正执行任务的线程以及第一个要执行的任务。
3. execute() 方法
execute() 是提交任务的核心方法。
它的核心逻辑就是:
1. 工作线程数小于核心线程数,创建核心线程 2. 核心线程已满,任务进入队列 3. 队列满了,创建非核心线程 4. 线程数达到最大值,执行拒绝策略
4. runWorker() 方法
Worker 启动后会执行 runWorker() 方法。
核心逻辑是:
先执行 firstTask 然后不断从任务队列中获取任务执行
线程池能够复用线程,关键就在这里。
线程执行完一个任务后不会立即销毁,而是继续从队列中获取下一个任务。
5. getTask() 方法
getTask() 用于从任务队列中获取任务。
核心线程默认会一直阻塞等待任务,非核心线程空闲超过 keepAliveTime 后会被回收。
如果开启:
allowCoreThreadTimeOut(true);
核心线程也可以超时回收。
十、线程池如何调优
线程池调优的核心是:
让线程数、队列大小、拒绝策略和任务耗时匹配系统真实处理能力
1. 根据任务类型设置线程数
CPU 密集型任务主要消耗 CPU,例如加密、压缩、计算等。
推荐:
线程数 = CPU 核心数 + 1
IO 密集型任务主要时间消耗在等待 IO,例如数据库查询、HTTP 调用、RPC 调用等。
推荐:
线程数 = 2 * CPU 核心数
更通用的估算公式:
线程数 = CPU 核心数 * CPU 利用率 * (1 + 等待时间 / 计算时间)
2. 使用有界队列
生产环境不建议使用无界队列。
队列太小容易触发拒绝策略;队列太大可能导致任务堆积、响应时间变长,甚至 OOM。
3. 设置合理拒绝策略
核心任务不要直接丢弃。
可以使用:
AbortPolicy CallerRunsPolicy 自定义拒绝策略
自定义拒绝策略中可以记录日志、发送告警、写入数据库或 MQ 做补偿。
4. 配置线程名称
自定义线程名称方便线上排查问题。
例如:
order-pool-1 payment-pool-1
5. 关注监控指标
线程池调优不能只靠经验,需要结合监控数据。
常见指标:
当前线程数 活跃线程数 队列长度 已完成任务数 拒绝任务数 任务平均耗时 P95、P99 耗时 CPU 使用率 内存使用率 GC 情况 下游服务响应时间
如果队列持续堆积,说明任务处理速度跟不上提交速度。
如果 CPU 很高,可能是线程数过多或者任务本身 CPU 消耗较大。
如果 CPU 不高但任务处理慢,可能是 IO 或下游服务成为瓶颈。
十一、总结
线程池的核心作用是:
复用线程、控制并发、管理任务、保护系统资源
学习线程池时,需要重点掌握:
1. 核心参数 2. 执行流程 3. 常见线程池类型 4. 拒绝策略 5. 线程池状态 6. ThreadPoolExecutor 实现原理 7. 线程池调优思路
其中最重要的执行流程是:
核心线程 → 任务队列 → 非核心线程 → 拒绝策略
线程池参数没有固定最优解。实际开发中需要结合业务场景、任务类型、机器资源、任务耗时、下游服务能力和压测结果综合调整。
到此这篇关于Java 线程池详解:核心参数、执行流程、拒绝策略、实现原理与调优的文章就介绍到这了,更多相关java内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Java多线程 两阶段终止模式Two-Phase Termination Patter
这篇文章主要介绍了Java多线程 两阶段终止模式Two-Phase Termination Patter,该模式有两个角色,分别是Terminator,终止者,负责接收终止请求,执行终止处理,处理完成后再终止自己。TerminationRequester终止请求发出者,用来向Terminator发出终止请求,需要的朋友可以参考一下2021-10-10
Linux Ubuntu系统下配置JDK环境、MySQL环境全过程
众所周知Ubuntu是一种基于Linux的操作系统,它提供了一个稳定、安全和易于使用的环境,下面这篇文章主要给大家介绍了关于Linux Ubuntu系统下配置JDK环境、MySQL环境的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下2024-07-07


最新评论