Java线程池
约 2220 字大约 7 分钟
2025-09-06
一、线程池
线程池是一种管理线程的机制,它可以提前创建好多个线程,放在线程池中,在需要的时候拿出来直接使用,使用完再放回去,下次使用,再拿出来使用。
1、线程池原理
线程池通过复用线程、控制并发强度和任务调度,提升系统性能并优化资源利用。以下是其工作原理的详细解析。
组成部分:
- 核心线程池:一直保持存活的线程,用于执行任务。
- 任务队列:用于存储待执行的任务。
- 临时线程池:当核心线程池满了,但是任务队列未满时,会创建临时线程来执行任务。
- 线程工厂:用于创建新的线程。
- 拒绝策略:当线程池和任务队列都满了,拒绝新的任务。
工作流程:
- 提交任务:当有任务需要执行时,线程池会先判断核心线程池是否已满。如果未满,会创建一个新的核心线程来执行任务。如果已满,会将任务放入任务队列中等待执行。
- 执行任务:当核心线程池中的线程空闲时,会从任务队列中取出任务并执行。
- 拒绝任务:如果任务队列已满,并且临时线程池中的线程数已达最大线程数,线程池会根据拒绝策略拒绝新的任务。
2、Java并发库
Executor框架是Java提供的一个用于执行异步任务的并发库,它提供了线程池的实现。类图体系如下:

Executor框架的核心类有以下几个:
- Executor:执行器接口,定义了执行任务的方法。
- ExecutorService:执行器服务接口,继承自Executor,定义了管理线程池的方法。
- ThreadPoolExecutor:线程池执行器类,继承自AbstractExecutorService,实现了线程池的具体功能。
- Executors:工具类,提供了创建不同类型线程池的静态方法。
3、线程池参数
线程池的参数有以下几个:
- corePoolSize:核心线程数,线程池创建时的初始线程数。
- maximumPoolSize:最大线程数,线程池允许的最大线程数。
- keepAliveTime:线程空闲时间,超过该时间的非核心线程会被销毁。
- unit:时间单位,keepAliveTime的单位。
- workQueue:任务队列,用于存储待执行的任务。
- threadFactory:线程工厂,用于创建新的线程。
- handler:拒绝策略,当线程池和任务队列都满了,拒绝新的任务。
4、线程池类型
Java提供了四种类型的线程池,分别是:
- 固定线程池(FixedThreadPool):核心线程数和最大线程数相等,线程数固定不变。
- 缓存线程池(CachedThreadPool):核心线程数为0,最大线程数为Integer.MAX_VALUE,线程数根据任务量动态调整。
- 单线程池(SingleThreadExecutor):核心线程数和最大线程数都为1,任务按序执行。
- 定时线程池(ScheduledThreadPool):核心线程数为0,最大线程数为Integer.MAX_VALUE,线程数根据任务量动态调整,任务按指定时间执行。
5、任务队列类型
线程池的任务队列类型有以下几种:
ArrayBlockingQueue:基于数组的有界阻塞队列。
LinkedBlockingQueue:基于链表的无界阻塞队列。
SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等待另一个线程调用移除操作,否则插入操作一直阻塞。
PriorityBlockingQueue:基于优先级的无界阻塞队列。
任务队列的选择:
如果任务数量固定,任务执行时间短,建议使用ArrayBlockingQueue。
如果任务数量不确定,任务执行时间长,建议使用LinkedBlockingQueue。
如果任务数量不确定,任务执行时间短,建议使用SynchronousQueue。
如果任务数量不确定,任务执行时间不确定,建议使用PriorityBlockingQueue。
6、线程池拒接策略
当线程池的线程数达到最大线程数,且任务队列也已满时,线程池会使用拒绝策略来处理新提交的任务。
Java提供了四种标准的拒绝策略,它们都实现了RejectedExecutionHandler接口。
AbortPolicy(中止策略):默认的拒绝策略,直接抛出RejectedExecutionException异常,阻止系统正常运行。
CallerRunsPolicy(调用者运行策略):由提交任务的线程自己来执行任务,这种策略会降低新任务的提交速度,影响程序的整体性能,但不会丢弃任务。
DiscardPolicy(丢弃策略):直接丢弃无法处理的任务,不会抛出异常。
DiscardOldestPolicy(丢弃最旧策略):丢弃任务队列中最旧的任务(即队列头部的任务),并尝试提交新的任务。
拒绝策略的选择:
如果系统不能容忍任务被丢弃,建议使用AbortPolicy。
如果系统可以容忍任务被丢弃,建议使用DiscardPolicy或DiscardOldestPolicy。
如果系统希望任务能够被执行,只是执行的时间可能会晚一些,建议使用CallerRunsPolicy。
7、线程池的生命周期
Java线程池ThreadPoolExecutor类实现的状态如下::
RUNNING(运行中):线程池创建后的初始状态,可以接收新任务并处理队列中的任务。
SHUTDOWN(关闭中):调用shutdown()方法后进入此状态,不再接收新任务,但会继续处理队列中的任务。
STOP(停止):调用shutdownNow()方法后进入此状态,不再接收新任务,不再处理队列中的任务,并中断正在执行的任务。
TIDYING(整理中):所有任务都已终止,workerCount(工作线程数)为0,线程池即将进入TERMINATED状态。
TERMINATED(终止):线程池完全终止,terminated()方法已被调用。
状态转换流程:
RUNNING -> SHUTDOWN:调用shutdown()方法
RUNNING -> STOP:调用shutdownNow()方法
SHUTDOWN -> TIDYING:任务队列为空且所有工作线程都已终止
STOP -> TIDYING:所有工作线程都已终止
TIDYING -> TERMINATED:线程池中的所有任务都已执行完毕,线程数为0。
二、线程池使用
线程池的类型有四类,但是常用的就前两种,下面以具体例子展示线程池如何使用。
1、单线程池
// 创建单线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 提交任务
executorService.submit(() -> {
// 异步执行的代码
});源码
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}由源码可以看出,单线程池是一个指定线程数始终为1的线程池,任务在线程池中是按顺序执行的。
**单线程池的应用场景:**
- 需要按顺序执行的场景,比如数据库操作、文件操作、日志记录、缓存刷新等。
2、固定线程池
// 创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 提交任务
executorService.submit(() -> {
// 异步执行的代码
});
// 提交任务
executorService.submit(new Runnable() {
@Override
public void run() {
// 异步执行的代码
}
});
// 关闭线程池,阻止继续添加任务。
executorService.shutdown();
// 等待任务完成
boolean isTerminated = executorService.awaitTermination(10, TimeUnit.SECONDS);
if (!isTerminated) {
// 超时处理, 强制关闭线程池
executorService.shutdownNow();
}源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}由源码可以看出,固定线程池是一个指定了线程数的线程池。
固定线程池的应用场景:
- 任务数量固定,任务执行时间短,建议使用固定线程池。
3、自定义线程池
简单的异步任务可以使用上面的固定线程池解决我们的业务问题,但在秉持这资源的合理利用和特殊的业务场景下,我们需要自定义线程池。
例如:并发异步处理任务的时候,我们需要在任务多的时候,可以多启动些线程,任务少的时候,又可以减少空闲线程。
// 创建线程池
ThreadPoolExecutor executorService = new ThreadPoolExecutor(5, 20, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
// 提交任务
executorService.submit(() -> {
// 异步执行的代码
});
// 提交任务
executorService.submit(new Runnable() {
@Override
public void run() {
// 异步执行的代码
}
});自定义线程池的注意事项:
- 线程池的大小需要根据实际情况来确定,不能太大也不能太小,需要根据实际业务异步任务的数量和并发量来确定。
- 线程池的队列大小也需要根据实际情况来确定,根据业务需求来选择是有界队列还是无界队列。
- 线程池的拒绝策略需要根据实际情况来确定,是使用默认的拒绝策略,还是自定义拒绝策略。
