java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java 线程池详解

Java 线程池详解:核心参数、执行流程、拒绝策略、实现原理与调优

作者:jianjinwssy

在Java中,线程池(ThreadPoolExecutor)是并发编程中非常重要的一个工具,它能够有效地管理和复用线程,从而提高应用程序的性能和资源利用率,本文介绍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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文