在Java并发编程中,ThreadPoolExecutor
是java.util.concurrent
包中提供的一个非常重要的线程池实现。它允许开发者控制线程池的行为,如核心线程数、最大线程数、空闲线程存活时间等,并提供了多种拒绝策略来处理超出容量的任务。
ThreadPoolExecutor的基本概念
核心参数
- corePoolSize:线程池的核心线程数。即使没有任务执行,这些线程也会一直存活(除非它们的空闲时间超过了
keepAliveTime
)。 - maximumPoolSize:线程池的最大线程数。当任务数量超过核心线程数时,新创建的线程数不会超过这个值。
- keepAliveTime:当线程数大于核心线程数时,多余的线程空闲多久后会被销毁。只有当线程池中的线程数大于
corePoolSize
时,keepAliveTime
才会起作用。 - unit:
keepAliveTime
的时间单位。 - workQueue:用来存放任务的工作队列。当提交的任务数量超过核心线程数时,额外的任务会暂时存储在这个队列中等待执行。
- threadFactory:创建新线程的工厂。可以通过它定制线程的创建方式。
- handler:拒绝策略,当任务数量超过最大限制时(即队列已满且线程数达到
maximumPoolSize
),如何处理新来的任务。
主要方法
- execute(Runnable command):执行给定的任务。
- submit(Callable task):提交一个可返回结果的任务,并返回一个
Future<T>
对象,该对象可用于获取任务的结果。 - submit(Runnable task, T result):提交一个任务,并返回一个
Future<T>
对象,该对象可用于获取任务的结果。 - shutdown():停止接收新任务,但继续执行已经提交的任务。
- shutdownNow():立即终止所有正在执行的任务,并返回一个包含尚未执行的任务列表。
创建ThreadPoolExecutor
创建一个ThreadPoolExecutor
实例通常需要指定上面提到的一些参数。例如,以下代码创建了一个线程池,它有2个核心线程,最大线程数为5,使用LinkedBlockingQueue
作为工作队列,并定义了一个简单的拒绝策略:
import java.util.concurrent.*;
public class ThreadPoolExecutorExample {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // corePoolSize
5, // maximumPoolSize
60, // keepAliveTime
TimeUnit.SECONDS, // unit
new LinkedBlockingQueue<>(10), // workQueue
Executors.defaultThreadFactory(), // threadFactory
new ThreadPoolExecutor.AbortPolicy() // handler
);
// 提交任务
for (int i = 0; i < 15; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("Task ID: " + taskId + " is running by " + Thread.currentThread().getName());
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 关闭线程池
executor.shutdown();
}
}
拒绝策略
当线程池无法接受新任务时,可以采用不同的拒绝策略来处理这些任务:
- AbortPolicy:抛出
RejectedExecutionException
异常。 - CallerRunsPolicy:调用者的线程直接执行任务。
- DiscardOldestPolicy:丢弃队列中最老的任务,并尝试再次提交新任务。
- DiscardPolicy:简单地丢弃任务而不做任何通知。
线程池状态
ThreadPoolExecutor
有以下几种状态:
- RUNNING:接受新任务,并处理等待队列中的任务。
- SHUTDOWN:不再接受新任务,但会继续处理等待队列中的任务。
- STOP:不再接受新任务,并中断正在执行的任务。
- TIDYING:所有任务完成,线程池进入收尾状态。
- TERMINATED:收尾完成后进入的状态。
线程池生命周期
线程池的生命周期大致如下:
- 创建线程池。
- 当提交任务时,首先检查当前线程数是否小于
corePoolSize
,如果是,则创建新线程来执行任务。 - 如果当前线程数等于
corePoolSize
并且还有任务未处理,则将任务放入workQueue
。 - 如果
workQueue
已满,则创建新线程(不超过maximumPoolSize
)来执行任务。 - 如果当前线程数达到
maximumPoolSize
并且workQueue
已满,则根据拒绝策略处理新任务。 - 当线程池处于
SHUTDOWN
状态时,不再接受新任务,但会继续处理等待队列中的任务。 - 当线程池处于
STOP
状态时,不再接受新任务,并中断正在执行的任务。 - 当所有任务完成并且线程池处于
SHUTDOWN
状态时,线程池最终进入TERMINATED
状态。
使用场景
- 短期任务:当任务执行时间较短时,可以使用较小的核心线程数和较大的工作队列。
- 长期任务:对于执行时间较长的任务,可能需要较大的核心线程数,以避免大量线程长时间处于空闲状态。
- 固定线程数:当任务数量相对稳定时,可以设置核心线程数等于最大线程数,这样线程池中的线程数量就固定不变了。
- 周期性任务:可以使用定时任务框架,如
ScheduledThreadPoolExecutor
,它也是基于ThreadPoolExecutor
的。
总结
ThreadPoolExecutor
是Java中实现线程池的一种强大工具,它可以灵活地配置线程池的各种参数,以适应不同的应用场景。合理配置线程池可以显著提高系统的响应速度和吞吐量,同时减少资源消耗。在设计线程池时,应该根据具体的需求仔细考虑各种参数的选择,以确保线程池能够高效稳定地运行。