java线程池详解
线程池
一、线程池的核心概念
线程池本质上是一个管理线程的容器,它预先创建一定数量的线程,复用这些线程来执行多个任务,避免了频繁创建和销毁线程带来的性能开销。
1. 为什么要用线程池?
- 降低资源消耗:复用已创建的线程,减少线程创建 / 销毁的系统开销。
- 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
- 便于管理:统一控制线程的数量、优先级、任务队列等,避免无限创建线程导致 OOM(内存溢出)。
2. 线程池的核心参数(ThreadPoolExecutor)
Java 中线程池的核心实现是java.util.concurrent.ThreadPoolExecutor,其构造方法的核心参数如下:
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数(常驻线程数)
int maximumPoolSize, // 最大线程数(线程池能容纳的最大线程数)
long keepAliveTime, // 非核心线程的空闲存活时间
TimeUnit unit, // keepAliveTime的时间单位
BlockingQueue<Runnable> workQueue, // 任务等待队列
ThreadFactory threadFactory, // 线程创建工厂(自定义线程名称、优先级等)
RejectedExecutionHandler handler // 拒绝策略(任务过多时的处理方式)
)
参数详解:
-
核心线程数:线程池长期保持的线程数量,即使线程空闲也不会销毁(除非设置allowCoreThreadTimeOut=true)。
-
最大线程数:线程池允许创建的最大线程数,核心线程满了 + 队列满了才会创建非核心线程。
-
空闲存活时间:非核心线程空闲超过该时间会被销毁,释放资源。
-
任务队列:核心线程都在忙时,新任务会进入队列等待,常见的队列类型:
- ArrayBlockingQueue:有界数组队列(推荐,避免队列无限扩容)。
- LinkedBlockingQueue:无界链表队列(默认容量Integer.MAX_VALUE,易导致 OOM)。
- SynchronousQueue:直接传递队列(不存储任务,直接交给线程执行)。
-
拒绝策略:当线程数达到最大值 + 队列满时,新任务的处理方式:
- AbortPolicy(默认):直接抛出RejectedExecutionException。
- CallerRunsPolicy:由提交任务的线程自己执行。
- DiscardPolicy:直接丢弃新任务,无任何提示。
- DiscardOldestPolicy:丢弃队列中最旧的任务,尝试加入新任务。
二、线程池的工作流程

三、常用的线程池(Executors 工具类)
Java 提供了Executors工具类快速创建线程池,但生产环境不推荐直接使用(多数是无界队列,易导致 OOM),以下是常见类型:
| 线程池类型 | 核心参数 | 适用场景 |
|---|---|---|
| FixedThreadPool | 核心线程数 = 最大线程数,无界队列 | 执行长期稳定的任务(如后台任务) |
| SingleThreadExecutor | 核心线程数 = 最大线程数 = 1,无界队列 | 任务需串行执行的场景 |
| CachedThreadPool | 核心线程数 = 0,最大线程数 = Integer.MAX_VALUE | 短期、高频的异步任务(如临时请求) |
| ScheduledThreadPool | 核心线程数固定,支持定时 / 延迟任务 | 定时任务(如定时清理缓存) |
示例:自定义线程池(推荐)
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 1. 定义核心参数
int corePoolSize = 5; // 核心线程数
int maximumPoolSize = 10; // 最大线程数
long keepAliveTime = 60L; // 非核心线程空闲60秒销毁
TimeUnit unit = TimeUnit.SECONDS;
// 有界任务队列(容量100)
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);
// 自定义线程工厂(设置线程名称,便于排查问题)
ThreadFactory threadFactory = new ThreadFactory() {
private int count = 1;
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("custom-thread-" + count++);
return thread;
}
};
// 拒绝策略:提交任务的线程自己执行
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
// 2. 创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, unit,
workQueue, threadFactory, handler
);
// 3. 提交任务
for (int i = 0; i < 20; i++) {
int taskNum = i;
executor.execute(() -> {
System.out.println("任务" + taskNum + "由线程" + Thread.currentThread().getName() + "执行");
try {
Thread.sleep(1000); // 模拟任务执行
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 4. 关闭线程池(重要!)
executor.shutdown(); // 平缓关闭:等待已提交的任务执行完
// executor.shutdownNow(); // 强制关闭:中断所有正在执行的任务,返回未执行的任务列表
}
}
说明:
- execute():提交无返回值的任务(Runnable)。
- submit():提交有返回值的任务(Callable),返回Future对象,可获取任务结果。
- submit():提交有返回值的任务(Callable),返回Future对象,可获取任务结果。
四、线程池的最佳实践
1、避免使用 Executors 默认创建:手动创建ThreadPoolExecutor,指定有界队列和合理的拒绝策略。
2、核心线程数设置:
- CPU 密集型任务(如计算):核心线程数 = CPU 核心数 + 1。
- IO 密集型任务(如网络请求、文件读写):核心线程数 = CPU 核心数 * 2。
3、监控线程池状态:通过ThreadPoolExecutor的方法监控(getActiveCount()、getQueueSize()、getCompletedTaskCount())。
4、处理任务异常:
- 任务内捕获所有异常,避免线程终止。
- 使用submit()提交任务,通过Future.get()捕获异常。
线程池优化
一、核心参数的精细化配置(最基础也最关键)
线程池的性能瓶颈往往源于参数配置不合理,优化的核心是让核心线程数、最大线程数、任务队列 与业务场景匹配。
- 核心线程数(corePoolSize)优化
核心线程数决定了线程池的基础处理能力,需根据任务类型精准设置:
- CPU 密集型任务(如数据计算、排序):
这类任务 CPU 利用率高,线程数过多会导致上下文切换开销增大。
推荐值:CPU核心数 + 1(+1 是为了防止某个线程因缺页中断等原因阻塞时,还有线程能利用 CPU)。
可通过Runtime.getRuntime().availableProcessors()获取 CPU 核心数。 - IO 密集型任务(如数据库查询、网络请求、文件读写):
这类任务线程大部分时间在等待 IO,CPU 利用率低,可配置更多线程。
推荐值:CPU核心数 * 2 或 CPU核心数 / (1 - 阻塞系数)(阻塞系数通常取 0.8~0.9)。 - 混合任务:
拆分 CPU 密集和 IO 密集任务到不同线程池,避免互相干扰。
- 任务队列(workQueue)优化
核心原则:必须使用有界队列,杜绝无界队列(如默认的 LinkedBlockingQueue)导致的 OOM。
-
队列类型选择:
- 任务执行时间短、数量多:用ArrayBlockingQueue(有界数组队列,内存占用固定,存取效率高)。
- 任务执行时间长、优先级有差异:用PriorityBlockingQueue(优先级队列,确保重要任务先执行)。
- 需直接传递任务(无队列缓冲):用SynchronousQueue(配合 CachedThreadPool 风格的线程池)。
-
队列容量设置:
队列容量 = (峰值任务数 - 核心线程数 * 每秒处理任务数)* 平均任务执行时间
避免队列过大(任务等待时间长)或过小(频繁创建非核心线程,增加开销)。
- 最大线程数(maximumPoolSize)与空闲时间(keepAliveTime)优化
- 最大线程数:核心线程数 + 队列容量无法处理峰值任务时的补充,建议不超过核心线程数的 2~3 倍(IO 密集型可适当提高),避免线程过多导致上下文切换。
- 空闲时间:非核心线程的存活时间,IO 密集型任务可设短(如 30 秒),CPU 密集型可设长(如 60 秒),核心线程可通过allowCoreThreadTimeOut(true)允许超时销毁(适用于任务量波动大的场景)。
4. 拒绝策略(RejectedExecutionHandler)优化
默认的AbortPolicy直接抛异常,生产环境需根据业务场景自定义或选择更合理的策略:
- 核心业务任务:用CallerRunsPolicy(提交任务的线程执行,降低任务丢失风险),或自定义拒绝策略(如记录日志 + 任务入消息队列重试)。
- 非核心业务任务:用DiscardOldestPolicy(丢弃最旧任务)或DiscardPolicy(丢弃新任务),但需记录丢弃日志。
自定义拒绝策略示例:
RejectedExecutionHandler customHandler = (r, executor) -> {
// 1. 记录任务丢弃日志(关键)
System.err.println("任务" + r.toString() + "被拒绝,当前线程池活跃数:" + executor.getActiveCount());
// 2. 核心任务可尝试重试,或写入MQ/数据库兜底
if (!executor.isShutdown()) {
try {
// 尝试将任务放入队列(阻塞1秒)
executor.getQueue().offer(r, 1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
};
二、线程池的监控与调优
1. 核心监控指标
通过ThreadPoolExecutor的内置方法获取:
- getActiveCount():当前活跃线程数(正在执行任务的线程)。
- getQueue().size():队列中等待的任务数。
- getCompletedTaskCount():已完成的任务总数。
- getLargestPoolSize():线程池曾达到的最大线程数(判断是否达到最大线程数)。
- getRejectedExecutionCount():被拒绝的任务数(拒绝策略是否触发)。
2. 监控实现方式
- 简单监控:定时打印指标(如每 10 秒)。
- 集成监控系统:将指标上报到 Prometheus、Micrometer 等,结合 Grafana 可视化。
监控代码示例:
ScheduledExecutorService monitor = Executors.newSingleThreadScheduledExecutor();
// 每10秒监控一次
monitor.scheduleAtFixedRate(() -> {
ThreadPoolExecutor executor = (ThreadPoolExecutor) threadPool;
System.out.println("===== 线程池监控 =====");
System.out.println("活跃线程数:" + executor.getActiveCount());
System.out.println("等待任务数:" + executor.getQueue().size());
System.out.println("已完成任务数:" + executor.getCompletedTaskCount());
System.out.println("拒绝任务数:" + executor.getRejectedExecutionCount());
System.out.println("======================");
}, 0, 10, TimeUnit.SECONDS);
3. 动态调优
基于监控数据实时调整参数(线程池支持运行时修改核心参数):
// 运行时修改核心线程数
executor.setCorePoolSize(8);
// 运行时修改最大线程数
executor.setMaximumPoolSize(15);
// 运行时修改非核心线程空闲时间
executor.setKeepAliveTime(30, TimeUnit.SECONDS);
场景:高峰时段(如秒杀)临时提高最大线程数,低峰时段降低,减少资源占用。
三、使用层面的优化
1. 避免线程池滥用
- 按业务维度拆分线程池:如 “订单处理线程池”、“日志处理线程池”,避免一个线程池处理所有任务(核心任务被非核心任务阻塞)。
- 避免创建临时线程池:每次创建新线程池会导致线程频繁创建销毁,应复用全局线程池。
2. 任务层面优化
- 任务粒度适中:任务太小(如仅执行一行代码)会增加线程切换开销;任务太大(如执行 10 分钟的批处理)会导致线程长时间占用,降低并发度。
- 捕获任务内异常:线程池的线程若因未捕获异常终止,会导致线程池创建新线程,增加开销。
executor.execute(() -> {
try {
// 任务逻辑
} catch (Exception e) {
// 记录异常,避免线程终止
log.error("任务执行异常", e);
}
});
- 避免任务阻塞:IO 任务尽量使用异步 API(如 CompletableFuture),减少线程阻塞时间。
3. 线程池关闭优化
- 平缓关闭:使用shutdown()而非shutdownNow(),等待已提交的任务执行完毕。
- 优雅退出:关闭前等待任务执行完成,避免强制终止导致数据丢失。
// 关闭线程池
executor.shutdown();
// 等待60秒,确认所有任务执行完毕
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
// 超时后强制关闭
executor.shutdownNow();
}
四、高级优化:自定义线程池扩展
通过继承ThreadPoolExecutor重写钩子方法,实现更精细化的控制:
public class CustomThreadPool extends ThreadPoolExecutor {
// 任务执行前的钩子方法
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
// 记录任务开始时间、线程ID等
log.info("任务{}开始执行,线程:{}", r.toString(), t.getName());
}
// 任务执行后的钩子方法
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
// 记录任务结束时间、执行耗时,捕获异常
if (t != null) {
log.error("任务{}执行异常", r.toString(), t);
} else {
log.info("任务{}执行完成", r.toString());
}
}
// 线程池关闭时的钩子方法
@Override
protected void terminated() {
super.terminated();
log.info("线程池已关闭");
}
}
总结
- 参数优化:核心是根据任务类型(CPU/IO 密集)精准配置核心线程数、最大线程数,使用有界队列和业务适配的拒绝策略。
- 监控调优:通过监控活跃线程数、队列长度、拒绝任务数等指标,动态调整线程池参数,避免性能瓶颈。
- 使用优化:按业务拆分线程池、捕获任务异常、避免线程阻塞,确保线程池稳定高效运行。

浙公网安备 33010602011771号