1. 线程池概述
在Java中,线程池(ThreadPool)是一种管理线程的技术,通过预先创建并管理一组线程,来减少频繁创建和销毁线程所带来的开销,从而提高系统的响应速度和吞吐量。ThreadPoolExecutor
是Java并发包java.util.concurrent
中的一个核心类,它提供了丰富的线程池功能。
2. ThreadPoolExecutor的核心概念
- 线程池状态:ThreadPoolExecutor有五种状态,包括RUNNING、SHUTDOWN、STOP、TIDYING和TERMINATED。这些状态之间通过调用shutdown()和shutdownNow()等方法进行转换。
- 核心参数:
- corePoolSize:线程池中核心线程数的最大值。
- maximumPoolSize:线程池中能拥有的最大线程数。
- workQueue:用于缓存任务的阻塞队列。
- keepAliveTime和TimeUnit:表示非核心线程的空闲存活时间及其时间单位。
- handler:当线程池无法处理新任务时所使用的拒绝策略。
3. ThreadPoolExecutor的工作原理
当向线程池提交一个任务时,ThreadPoolExecutor会按照以下步骤处理:
- 判断当前线程数:如果线程池中的线程数小于corePoolSize,则创建新线程执行任务。
- 判断工作队列:如果线程数已达到corePoolSize,则将任务加入workQueue中等待。
- 判断最大线程数:如果workQueue已满,且线程数小于maximumPoolSize,则创建新线程执行任务。
- 拒绝任务:如果线程数已达到maximumPoolSize,且workQueue已满,则根据handler指定的策略拒绝新任务。
4. 源码分析
4.1 核心字段
ThreadPoolExecutor 类中定义了一些关键的字段,这些字段对于理解其工作原理至关重要:
- ctl:一个 AtomicInteger 类型的字段,用于保存线程池的状态和线程数量。高三位表示线程池的状态,低29位表示线程数量。
- workQueue:一个阻塞队列,用于存放待执行的任务。
- corePoolSize:核心线程数。
- maximumPoolSize:最大线程数。
- keepAliveTime:非核心线程的空闲存活时间。
- threadFactory:用于创建新线程的线程工厂。
- handler:拒绝策略处理器,当线程池无法处理新任务时调用。
4.2 构造方法
构造方法用于初始化线程池的各个参数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// ... 省略了部分参数校验和初始化代码 ...
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
// ... 省略其他字段初始化 ...
}
4.3 任务提交与执行
execute
方法是提交任务到线程池的主要入口:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
在 execute
方法中,首先会尝试将任务直接提交给一个工作线程执行(如果当前线程数小于 corePoolSize
)。如果提交失败或者当前线程数已达到 corePoolSize
,则尝试将任务添加到工作队列中。如果队列已满且线程数小于 maximumPoolSize
,则尝试创建新的工作线程。如果仍然无法提交任务(即线程池已关闭或已达到 maximumPoolSize
并且队列已满),则根据拒绝策略处理该任务。
4.4 线程创建与销毁
4.4.1 线程创建
线程创建主要发生在 addWorker 方法中。这个方法会根据需要添加新的工作线程到线程池中。
// 简化版的 addWorker 方法
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
// ...(省略了部分代码,如检查线程池状态等)
// 创建新的 Worker 线程
Worker w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
// ...(省略了部分代码,如工作线程数量限制检查等)
// 将 Worker 添加到 workers 集合中
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
// ...(省略了部分代码,如启动线程等)
return workerStarted;
}
}
// Worker 类的构造函数
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable {
// ...(省略了其他字段和方法)
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
// ...(省略了其他方法)
}
在 Worker
类的构造函数中,调用了 ThreadFactory
的 newThread
方法来创建一个新的线程,并将 Worker
实例作为 Runnable
传递给新线程。这样,当新线程启动时,它会调用 Worker
的 run
方法。
4.4.2 线程销毁
线程销毁通常发生在以下几种情况:
- 线程完成任务后自然退出:当
Worker
线程的run
方法执行完毕后,线程会自然退出。 - 线程池关闭:当调用
shutdown
或shutdownNow
方法关闭线程池时,会中断所有工作线程。
在 Worker
的 run
方法中,线程会不断地从工作队列中获取任务并执行,直到没有任务可执行或者线程被中断。
// Worker 类的 run 方法
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
// ...(省略了部分代码,如初始化线程等)
try {
// 循环获取并执行任务
while (task != null || (task = getTask()) != null) {
// ...(省略了执行任务的相关代码)
}
} finally {
// ...(省略了部分代码,如处理线程退出的相关逻辑)
// 从 workers 集合中移除 Worker
workers.remove(w);
// ...(省略了其他清理工作)
}
}
// 线程池关闭时中断线程的方法
public void shutdownNow() {
// ...(省略了部分代码)
// 遍历所有 Worker,并中断它们的线程
for (Worker w : workers)
w.interruptIfStarted();
// ...(省略了其他关闭逻辑)
}
// Worker 类的 interruptIfStarted 方法
private void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
在 shutdownNow
方法中,线程池会遍历所有 Worker
,并调用它们的 interruptIfStarted
方法来中断线程。
在 ThreadPoolExecutor
的 shutdownNow
方法中,线程池会尝试停止所有正在执行的任务,并返回那些尚未开始执行的任务列表。这是通过中断所有工作线程并遍历工作队列来实现的。现在,我们来更详细地分析 shutdownNow
方法和与线程销毁相关的其他部分。
shutdownNow
方法试图立即停止所有正在执行的任务,停止处理正在等待的任务,并返回等待执行的任务列表。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess(); // 确保有权关闭
advanceRunState(STOP); // 更改线程池状态为 STOP
interruptWorkers(); // 中断所有工作线程
tasks = drainQueue(); // 尝试从工作队列中移除并返回所有任务
} finally {
mainLock.unlock();
}
tryTerminate(); // 尝试终止线程池
return tasks;
}
// 中断所有工作线程
private void interruptWorkers() {
for (Worker w : workers)
w.interruptIfStarted();
}
// 尝试从工作队列中移除并返回所有任务
private List<Runnable> drainQueue() {
BlockingQueue<Runnable> q = workQueue;
ArrayList<Runnable> taskList = new ArrayList<Runnable>();
q.drainTo(taskList);
if (!q.isEmpty()) {
for (Runnable r : q.toArray(new Runnable[0])) {
if (q.remove(r))
taskList.add(r);
}
}
return taskList;
}
// 尝试终止线程池
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) ||
isShutdown(c) && !workQueue.isEmpty())
return;
if (workerCountOf(c) != 0) { // 仍有工作线程在运行
interruptIdleWorkers(ONLY_ONE); // 中断一个空闲线程
return;
}
final ReentrantLock terminationLock = this.terminationLock;
terminationLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated(); // 钩子方法,可以在这里执行清理工作
} finally {
terminationLock.unlock();
ctl.set(ctlOf(TERMINATED, 0));
terminationCondition.signalAll(); // 通知所有等待线程池终止的线程
}
return;
}
} finally {
terminationLock.unlock();
}
}
}
线程销毁的其他细节
- 线程的中断:当线程被中断时(通过
interrupt()
方法),它可能会抛出InterruptedException
异常,或者可以通过Thread.isInterrupted()
方法检查中断状态。在Worker
类的runWorker
方法中,会检查任务执行过程中是否发生了中断,并据此决定是否退出循环或抛出异常。 - 清理工作:在
tryTerminate
方法中,当线程池状态变为TIDYING
时,会调用terminated
方法。这是一个保护点(hook method),可以在这里执行清理工作,如关闭资源等。 - 线程池状态转换:线程池的状态转换是线程安全的,通过
ctl
变量的原子操作来实现。状态的转换遵循一定的规则,确保在关闭或终止线程池时能够正确地处理任务和执行清理工作。 - 等待终止:
terminationCondition
是一个条件变量,用于等待线程池完全终止。当线程池状态变为TERMINATED
时,会通知所有等待的线程。
通过上面的分析,可以看到 ThreadPoolExecutor
在线程创建和销毁方面做了大量的工作,以确保线程池能够高效地管理线程资源,并在需要时能够快速地停止所有任务并释放资源。
4.5 工作线程 Worker
Worker
是 ThreadPoolExecutor
的一个内部类,它继承了 Thread
类,并实现了 Runnable
接口。每个 Worker
对象都持有一个任务队列,用于从线程池的工作队列中获取任务并执行。
4.6 线程池状态管理
ThreadPoolExecutor
使用了一个原子整数 ctl
来同时表示线程池的状态和线程数量。通过位运算来读取和更新这个值,实现了线程安全的状态管理。
4.7 拒绝策略
当线程池无法处理新任务时(例如,线程池已关闭、线程数量已达到上限并且工作队列已满),ThreadPoolExecutor
会根据设置的拒绝策略来处理这些任务。Java 提供了四种内置的拒绝策略:
- ThreadPoolExecutor.AbortPolicy:直接抛出
RejectedExecutionException
异常。 - ThreadPoolExecutor.CallerRunsPolicy:在调用
execute
方法的线程中运行被拒绝的任务。 - ThreadPoolExecutor.DiscardPolicy:不处理被拒绝的任务,直接丢弃。
- ThreadPoolExecutor.DiscardOldestPolicy:丢弃工作队列中的最老的任务,然后尝试重新提交被拒绝的任务。
也可以通过实现 RejectedExecutionHandler 接口来定义自己的拒绝策略。
5. 使用场景
5.1 异步处理
- 当有一些耗时的任务需要执行,但又不希望这些任务阻塞主线程时,可以使用
ThreadPoolExecutor
。例如,在 Web 应用程序中,可能会使用线程池来处理用户上传的文件、发送电子邮件或执行其他后台任务。
5.2 提高系统吞吐量
- 当系统中有大量的小任务需要并发执行时,使用
ThreadPoolExecutor
可以显著提高系统的吞吐量。通过控制线程池的大小,可以确保系统有足够的资源来处理这些任务,同时避免创建过多的线程导致系统资源耗尽。
5.3 任务调度
ThreadPoolExecutor
可以与ScheduledThreadPoolExecutor
一起使用,实现定时或周期性地执行任务。这在需要定期执行清理、备份、报告等任务时非常有用。
5.4 并发限制
- 在某些情况下,可能需要限制并发执行的线程数量。例如,可能正在访问一个外部系统或数据库,并且知道该系统或数据库只能处理一定数量的并发请求。通过使用
ThreadPoolExecutor
,可以确保不会超过这个限制,从而避免可能的性能问题或错误。
5.5 隔离资源
- 如果应用程序包含多个独立的组件或模块,并且每个组件都需要执行一些后台任务,那么使用多个独立的线程池可以隔离这些资源。这样,一个组件中的任务就不会影响另一个组件的性能或稳定性。
5.6 优雅地处理失败和拒绝的任务
ThreadPoolExecutor
允许指定一个拒绝策略来处理无法提交到线程池的任务。这可以在系统资源不足或达到并发限制时提供一个优雅的降级策略。例如,可以将拒绝的任务记录到日志中、保存到数据库中或发送警报通知。
5.7 易于管理和监控
- 通过
ThreadPoolExecutor
,可以轻松地获取有关线程池状态和任务执行情况的统计信息。这有助于监控系统的性能、识别潜在的问题并进行相应的调整。
5.8 示例场景
- Web 服务器:处理用户请求时,可以使用线程池来并发地处理多个请求,提高响应速度。
- 批量处理:在需要处理大量数据(如文件、数据库记录等)的场景中,可以使用线程池来并行处理数据块,加快处理速度。
- 后台任务:在需要定期执行后台任务(如清理缓存、发送通知等)的场景中,可以使用
ScheduledThreadPoolExecutor
来实现定时或周期性的任务调度。
6. 线程池的扩展与调优
ThreadPoolExecutor
提供了丰富的扩展性和可配置性,开发人员可以根据实际需求来定制线程池的行为。例如,可以通过实现RejectedExecutionHandler
接口来定义自定义的拒绝策略,或者通过调整核心参数来优化线程池的性能。
7. 总结
ThreadPoolExecutor
是Java并发编程中不可或缺的一部分,它通过池化技术来降低线程创建和销毁的开销,提高系统的响应速度和吞吐量。通过对ThreadPoolExecutor
的深入理解和源码分析,可以更好地掌握线程池的工作原理和使用技巧,从而在实际开发中更加高效地使用线程池。同时,也可以根据实际需求来定制和扩展线程池的功能,以满足不同的应用场景。