线程池浅析 ThreadPoolExecutor FutureTask
前言
程序中使用 多线程 的场景,可以使用 线程池 来 创建、管理 线程。使用 线程池 可以提高程序的性能,减少每个任务调用开销,并且允许我们针对具体的场景限制资源,比如 线程数、任务数 等
ThreadPoolExecutor
我们在程序中创建的 线程池 一般使用 ThreadPoolExecutor(包括 Executors 的相关方法创建的也是 ThreadPoolExecutor)
ThreadPoolExecutor 允许我们指定 核心线程数、最大线程数、任务队列、线程空闲时间 等属性
接下来我们对 线程池 的重要属性进行解读
corePoolSize & maximumPoolSize
当我们调用 ThreadPoolExecutor#execute
方法给线程池分配一个任务
- 如果此时 线程池 中运行的线程数小于
corePoolSize
,则即便当前有线程空闲,线程池 也会创建一个新的线程来执行目标任务 - 如果 线程池 中允许的线程数已经达到
corePoolSize
,则目标任务会被添加到 任务队列,如果队列满了才会继续创建新的线程直到线程数达到maximumPoolSize
corePoolSize
和maximumPoolSize
属性由 ThreadPoolExecutor 的构造方法指定,同时也提供了setCorePoolSize
和setMaximumPoolSize
方法设置- 同时还提供了
prestartCoreThread
和prestartAllCoreThreads
方法,允许提前创建对应数量(或者全部)的core threads
(核心线程)
threadFactory
执行任务的线程由我们指定的 ThreadFactory
创建,如果不指定,ThreadPoolExecutor 则默认由 Executors#defaultThreadFactory
方法返回的 DefaultThreadFactory 创建,该工厂创建的线程拥有同样的 线程组、优先级、守护状态 等
keepAliveTime
默认情况下,如果运行的线程数超过 corePoolSize
,则超过的这部分线程如果空闲超过 keepAliveTime
,便会被销毁,此举利于节省开销、管理资源
如果想同样对 corePoolSize
数量内的线程生效,则可以调用 allowCoreThreadTimeOut(true)
方法进行指定
允许空闲时长 keepAliveTime
同样可以在 构造方法 指定,也可以调用 setKeepAliveTime
方法设置
workQueue
任务队列,前文提到,当允许的线程数达到 corePoolSize
后,后续的任务会被放入 任务队列 中
ThreadPoolExecutor 的任务队列由 BlockingQueue 实现,基于 Queue 拓展了以下功能
- 当队列满时,允许插入操作
put
阻塞 - 当队列为空时,允许取出操作
take
阻塞
ThreadPoolExecutor 插入任务调用的 offer 方法,因为允许拒绝
任务(见后文),取出任务使用的是 take 和 poll(阻塞版)
关于常用的 BlockingQueue 的解读,可以参考下面几篇文章
【源码】JUC —— BlockingQueue ArrayBlockingQueue 浅析
【源码】JUC —— LinkedBlockingQueue 浅析
使用 Executors 类创建的线程池,默认使用 LinkedBlockingQueue
handler
如果任务队列满了,且运行的线程数已经达到 maximumPoolSize
,则之后的任务交由 handler
处理,ThreadPoolExecutor 默认实现了 4
种 RejectedExecutionHandler
CallerRunsPolicy
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
目标任务由 当前线程 执行执行,也相当于缓冲了任务的发放
AbortPolicy
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
直接抛出 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);
}
}
}
把排在 队首 的元素 poll
出来(相当于丢掉了),然后继续执行当前任务(当然也并 不能 保证执行成功)
构造方法
依次解读了 ThreadPoolExecutor 的几个核心属性后,我们看看 ThreadPoolExecutor 提供的构造方法
// 不指定 threadFactory 和 handler
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
// 不指定 handler
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
// 不指定 threadFactory
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
// 全构造
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize
,核心线程数maximumPoolSize
,最大线程数keepAliveTime
,允许线程空闲的最大时长unit
,keepAliveTime
的时间单位workQueue
,任务队列threadFactory
,如果没有提供,则使用默认的 DefaultThreadFactoryhandler
,如果没有指定,使用默认的 AbortPolicy,即抛出异常
FutureTask
我们看到 ThreadPoolExecutor#execute
方法接受一个 Runnable,同样 ThreadPoolExecutor 也接受 Callable,以获取异步执行的任务结果
ThreadPoolExecutor 的父类 AbstractExecutorService 实现了 ExecutorService 接口,ExecutorService 接口定义了 submit
相关方法,来支持异步任务结果的获取
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
Callable
Callable 类似与 Runnable,它提供了 call
方法,类似于 Runnable#run
,但 call
方法如何做到被 Thread类 调用?
Future
Future 用来抽象 异步任务 的结果返回,提供了 get
方法 阻塞式 地获取异步任务的执行结果,同样提供了 取消任务执行、任务状态获取 等相关方法
public interface Future<V> {
/**
* 取消执行中的异步操作,但并不保证取消成功,如下场景会失败
* 1)取消已经完成的操作
* 2)已被取消过
* 3)由于其他某些原因无法取消
*
* 如果取消的目标任务并未执行,那它必然不会再执行了
* 但如果目标任务已经在执行了,则它是否中断取决于
* 参数 mayInterruptIfRunning
*
* 执行该方法后调用 isDone 方法返回 true
* 该方法如果返回 true 则调用 isCancelled 方法也返回 true
*/
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
/**
* Done 表示
* 1)执行完成
* 2)执行异常
* 3)被取消
*/
boolean isDone();
// 阻塞直到异步任务返回结果
V get() throws InterruptedException, ExecutionException;
// 阻塞指定时间
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
RunnableFuture
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
RunnableFuture 接口就将 Runnable 和 Future 连接了起来
FutureTask
FutureTask,RunnableFuture 的实现类
- 它是一个 Runnable,因此可以被 Thread 执行,换句话说,可以作为 异步任务 被 线程池 执行
- 它是一个 Future,因此支持 异步任务 的执行以及结果的 阻塞式 获取
我们来看看 FutureTask 的构造方法
// 接受一个 Callable,直接赋值给属性 callable
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW;
}
// 接受一个 Runnable,由 Executors#callable 创建对应的 callable
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW;
}
- 允许直接指定一个
callable
- 允许指定一个
runnable
和返回结果result
,委托Executors#callable
创建对应的callable
,代码如下
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
private static final class RunnableAdapter<T> implements Callable<T> {
private final Runnable task;
private final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
// ...
}
通过 适配器 类 RunnableAdapter,将目标 Runnable 适配成对应的 Callable,适配器设计模式 的经典使用场景
AbstractExecutorService
最后我们看看 ThreadPoolExecutor 的父类 AbstractExecutorService 如何实现 submit
方法,以实现 线程池对 异步任务 的执行和结果的 阻塞式 获取
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
可以看到,submit
方法在将目标任务转换成 RunnableFuture 后交由 execute
方法执行,同时返回它以支持结果的 阻塞式 获取
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
方法 newTaskFor
就是创建对应的 RunnableFuture 并返回
示例
如此,我们对线程池 ThreadPoolExecutor 的属性和对异步任务的支持做了解读,给出示例 demo
public class ThreadPoolTest {
/**
* corePoolSize: 2
* maximumPoolSize: 4
* keepAliveTime: 1s
* workQueue: 容量为 4 的 LinkedBlockingQueue
* threadFactory: 默认的 DefaultThreadFactory
* handler: 默认的 AbortPolicy,抛异常
*/
ThreadPoolExecutor threadPoolExecutor
= new ThreadPoolExecutor(
2
, 4
, 1
, TimeUnit.SECONDS
, new LinkedBlockingQueue<>(4)
// , (r, t) -> System.out.println("reject handle")
);
// 执行任务耗时 2s
Runnable runnable = () -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
@Test
public void test1() throws InterruptedException {
// 如果调用此方法,在所有任务执行完成 1s 后,所有线程被销毁
// threadPoolExecutor.allowCoreThreadTimeOut(true);
/**
* 执行 10 个任务
* 第 1,2 个任务创建新的线程执行(耗时 2s)
* 第 3,4,5,6 个任务放到 workQueue 中
* 因为队列满了,第 7,8 个任务继续创建线程直到 maximumPoolSize(4)
* 之后的第 9,10 个任务抛出异常(做了处理)
*/
for (int i = 0; i < 10; i++) {
try {
threadPoolExecutor.execute(runnable);
} catch (RejectedExecutionException re) {
System.out.println("任务溢出了");
}
System.out.println("-----------------------");
System.out.println("ActiveCount: " + threadPoolExecutor.getActiveCount());
System.out.println("PoolSize: " + threadPoolExecutor.getPoolSize());
System.out.println("QueuedCount: " + threadPoolExecutor.getQueue().size());
System.out.println("-----------------------");
}
/**
* 2s 后第一轮任务执行完成
*/
TimeUnit.SECONDS.sleep(2);
System.out.println("--------------------------");
System.out.println("ActiveCount: " + threadPoolExecutor.getActiveCount());
System.out.println("PoolSize: " + threadPoolExecutor.getPoolSize());
System.out.println("QueuedCount: " + threadPoolExecutor.getQueue().size());
System.out.println("--------------------------");
/**
* 又 2s 后第二轮任务执行完成,所有任务执行完
*/
TimeUnit.SECONDS.sleep(2);
System.out.println("--------------------------");
System.out.println("ActiveCount: " + threadPoolExecutor.getActiveCount());
System.out.println("PoolSize: " + threadPoolExecutor.getPoolSize());
System.out.println("QueuedCount: " + threadPoolExecutor.getQueue().size());
System.out.println("--------------------------");
/**
* 线程空闲 1s 后,超过 corePoolSize 的线程被回收
*/
TimeUnit.SECONDS.sleep(1);
System.out.println("---------------------------");
System.out.println("ActiveCount: " + threadPoolExecutor.getActiveCount());
System.out.println("PoolSize: " + threadPoolExecutor.getPoolSize());
System.out.println("QueuedCount: " + threadPoolExecutor.getQueue().size());
System.out.println("---------------------------");
/**
* 日志打印如下,结合回顾线程池的创建过程
* --------------------------------------------
* ActiveCount: 1
* PoolSize: 1
* QueuedCount: 0
* --------------------------------------------
* --------------------------------------------
* ActiveCount: 2
* PoolSize: 2
* QueuedCount: 0
* --------------------------------------------
* --------------------------------------------
* ActiveCount: 2
* PoolSize: 2
* QueuedCount: 1
* --------------------------------------------
* --------------------------------------------
* ActiveCount: 2
* PoolSize: 2
* QueuedCount: 2
* --------------------------------------------
* --------------------------------------------
* ActiveCount: 2
* PoolSize: 2
* QueuedCount: 3
* --------------------------------------------
* --------------------------------------------
* ActiveCount: 2
* PoolSize: 2
* QueuedCount: 4
* --------------------------------------------
* --------------------------------------------
* ActiveCount: 3
* PoolSize: 3
* QueuedCount: 4
* --------------------------------------------
* --------------------------------------------
* ActiveCount: 4
* PoolSize: 4
* QueuedCount: 4
* --------------------------------------------
* 任务溢出了
* --------------------------------------------
* ActiveCount: 4
* PoolSize: 4
* QueuedCount: 4
* --------------------------------------------
* 任务溢出了
* --------------------------------------------
* ActiveCount: 4
* PoolSize: 4
* QueuedCount: 4
* --------------------------------------------
* --------------------------------------------
* ActiveCount: 3
* PoolSize: 4
* QueuedCount: 0
* --------------------------------------------
* --------------------------------------------
* ActiveCount: 0
* PoolSize: 4
* QueuedCount: 0
* --------------------------------------------
* --------------------------------------------
* ActiveCount: 0
* PoolSize: 2
* QueuedCount: 0
* --------------------------------------------
*/
}
volatile AtomicInteger atomicInteger = new AtomicInteger();
// 阻塞 1s 后返回
Callable<Integer> callable = () -> {
TimeUnit.SECONDS.sleep(1);
return atomicInteger.incrementAndGet();
};
@Test
public void test2() throws ExecutionException, InterruptedException {
for (int i = 0; i < 3; i++) {
System.out.println(threadPoolExecutor.submit(callable).get());
}
// 结果
// 1 2 3
}
}
总结
本章节介绍了线程池 ThreadPoolExecutor 类,详细解读了 ThreadPoolExecutor 的属性
同时,对 FutureTask 体系的几个接口和类做了简单的解析,以说明 ThreadPoolExecutor 如何支持 异步任务 的执行和对执行结果的 阻塞式 获取