线程池ThreadPoolExecutor
1、类关系
- Executor 是一个接口, 它是 Executor 框架的基础, 它将任务的提交与任务的执行分离开来。
- ExecutorService 接口继承了 Executor【原来接口是可以继承接口的】, 在其上做了一些shutdown()、 submit()的扩展, 可以说是真正的线程池接口。
- AbstractExecutorService 抽象类实现了 ExecutorService 接口中的大部分方法。
- ThreadPoolExecutor 是线程池的核心实现类, 用来执行被提交的任务。
2、构造函数参数解析
其构造函数有多个,我们来详解参数最多的那个
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) { … }
corePoolSize:核心线程数
线程池维护的最小线程数量,即核心线程数。
maximumPoolSize:最大线程数
线程池允许创建的最大线程数量。
keepAliveTime:空闲线程存活时间
当一个【可被回收的线程】的空闲时间大于keepAliveTime,就会被回收。
- 什么是可被回收的线程?
1、设置allowCoreThreadTimeout=true的核心线程
2、大于核心线程数的线程(非核心线程)
unit:keepAliveTime的时间单位
workQueue:工作队列或阻塞队列
BlockingQueue workQueue 是阻塞队列接口,它用于存放待执行的任务:
当提交的任务数大于corePoolSize,再提交的任务就存放在阻塞队列,任务调度时再从队列中取出任务。它仅仅用来存放被execute()方法提交的Runnable任务。阻塞队列实现了BlockingQueue接口。
阻塞队列是有容量的,其容量取决于 workQueue 实现的是哪一种。
有界的阻塞队列规定了最大容量,无界的没有规定容量。
注意:阻塞队列的容量的含义与corePoolSize、maximumPoolSize区分开。
阻塞队列常用于生产者和消费者的场景, 生产者是向队列里添加元素的线程,消费者是从队列里取元素的线程。 阻塞队列就是生产者用来存放元素、 消费者用来获取元素的容器。
JDK常用的5个阻塞队列,都实现了 BlockingQueue 接口, 也都是线程安全的:
· ArrayBlockingQueue: 一个由数组结构组成的有界阻塞队列。
· LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列。
· PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列。
· DelayQueue: 一个使用优先级队列实现的无界阻塞队列。
· SynchronousQueue: 一个不存储元素的阻塞队列。
BlockingQueue的基本方法:
插入 | 描述及特点 |
---|---|
add(E e) | 插入元素。当队列已满,此时 add 会抛出异常 |
offer(E e) | 插入成功会返回true |
put(E e) | 当队列已满,此时 put, 队列会一直阻塞生产者线程, 直到队列可用或者响应中断退出 |
移除/取出 | 描述及特点 |
---|---|
remove() | 直接移除,没有返回值。当队列为空,此时 remove 会抛出异常 |
poll() | 取出一个元素,如果没有返回null |
take() | 当队列为空, 如果消费者线程从队列里 take 元素, 队列会阻塞消费者线程, 直到队列不为空 |
threadFactory:线程工厂
创建线程的工厂,可以设定线程名、线程编号等。
handler:拒绝策略
当核心线程数已满,阻塞队列已满时,新提交的任务使用拒绝策略处理。可以自定义拒绝策略,拒绝策略需要实现 RejectedExecutionHandler 接口。
ThreadPoolExecutor里提供的拒绝策略有四种,以下为4个内部类的源码。
- AbortPolicy:丢弃任务,并抛出异常。(默认)
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
// 只抛出异常
throw new RejectedExecutionException(...);
}
}
- DiscardPolicy:丢弃任务,但是不抛出异常。
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
// 空实现
}
}
- DiscardOldestPolicy:丢弃队列最前面的任务,并执行当前任务。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll(); // 移除最前端的任务
e.execute(r); // 执行当前任务
}
}
}
- CallerRunsPolicy:由调用线程自行处理该任务。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
策略没有好坏,各有不同的应用场景。
3、提交任务
- execute():提交不需要返回值的任务, 所以无法判断任务是否被线程池执行成功。
- submit():提交需要返回值的任务。 线程池会返回一个 future 类型的对象, 通过这个 future 对象可以判断任务是否执行成功, 并且可以通过 future的 get()方法来获取返回值, get()方法会阻塞当前线程直到任务完成, 而使用 get(long timeout, TimeUnit unit) 方法则会阻塞当前线程一段时间后立即返回, 这时候有可能任务没有执行完。
4、关闭线程池
shutdown() 或 shutdownNow() 。
它们的原理是遍历线程池中的工作线程, 然后逐个调用线程的 interrupt 方法来中断线程, 所以无法响应中断的任务可能永远无法终止。
- isShutdown():调用这两个关闭方法中的任意一个,就会返回 true。
- isTerminaed ():当所有的任务都已关闭后, 才表示线程池关闭成功,才会返回 true。
通常调用 shutdown 方法来关闭线程池, 如果任务不一定要执行完,则可以调用 shutdownNow 方法
5、线程池的执行流程
对比上面两个图,可以把执行流程大致分为4种情况:(假设阻塞队列设置了容量)
-
线程池刚创建时,调用 execute() 方法添加一个任务,此时正在运行的线程数量 < corePoolSize,则马上创建新线程并运行这个任务。
-
调用 execute() 方法添加一个任务,此时核心线程数已满,阻塞队列未满,那么此任务就添加到阻塞队列,任务调度时再从队列中取出。
-
调用 execute() 方法添加一个任务,此时核心线程数已满,阻塞队列已满,累计提交任务数 < maximumPoolSize,那么此时也会创建新线程并运行这个任务。
-
调用 execute() 方法添加一个任务,此时核心线程数已满,阻塞队列已满,累计提交任务数 > maximumPoolSize,新提交的任务将按照拒绝策略处理。
6、线程池代码实例
int corePoolSize = 5;
int maximumPoolSize = 5;
int queueSize = 2;
RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy(); // 拒绝策略用的是直接丢弃
ThreadPoolExecutor poor = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), handler);
for (int i = 0; i < 50; i++) {
final int index = i;
poor.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println("第" + index + "个线程正在执行");
} catch (Exception e) {
System.out.println("异常: " + e.getMessage());
}
}
});
}
核心线程数和最大线程数都是5,阻塞队列长度是2,运行结果:
第0个线程正在执行
第5个线程正在执行
第2个线程正在执行
第6个线程正在执行
第3个线程正在执行
第1个线程正在执行
第4个线程正在执行
只执行了7个线程,其余任务均被丢弃。大家可以自行修改线程参数体会。