【JUC 并发编程】— 线程池(上)
作用
线程池主要有以下好处
- 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
类图
ThreadPoolExecutor 类图如下
Executor
JDK 是这么介绍 Executor 的
An object that executes submitted {@link Runnable} tasks. This interface provides a way of decoupling task submission from the mechanics of how each task will be run, including details of thread use, scheduling, etc.
Executor 接口执行提交的 Runnable 任务。该接口提供了一种把任务的提交和任务的执行(包括线程使用和调度的细节)解耦的方式。
Executor 英文意思是执行器,顾名思义,就是执行任务,所以该接口只有一个执行任务的方法
void execute(Runnable command);
ExecutorService
ExecutorService 继承自 Executor,正如其名字一样,它定义了一个服务,定义了一个完成的线程池的行为。可以提交任务,执行任务,关闭服务。
shutdown() 和 shutdownNow() 方法都可以用来关闭 ExecutorService,两者区别如下
- shutdown():在关闭前会等已提交的任务执行完。
- shutdowNow():不会等任务开始执行,并且尝试终止正在执行的任务。
submit() 扩展自 execute() 方法,区别在于 submit() 执行后任务后会返回 Future,Future 可以用来追踪任务执行的过程,比如取消任务执行或者等待任务执行完成。看看 Future 中定义的方法就一目了然
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
AbstractExecutorService
AbstractExecutorService 实现了 ExecutorService 接口,提供了 ExecutorService 执行方法的默认实现,包括 submit、invokeAny 和 invokeAll 方法。
submit 方法
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
可以看到,submit 方法中调用了 execute 方法,但是 AbstractExecutorService 并没有提供 execute 的默认实现,而是等子类去实现,这便是模板方法的应用。
ThreadPoolExecutor
ThreadPoolExecutor 继承自 AbstractExecutorService,完整了实现了线程池所有行为,是真正意义上可用的线程池。
执行策略
先来熟悉下线程池几个关键的属性
- corePoolSize:线程池中最小的工作线程数量
- maximumPoolSize:线程池最大线程数
- keepAliveTime:空闲线程等待执行任务的超时时间(纳秒)
- workQueue:任务缓存队列,用来存放等待执行的任务
- handler:任务拒绝策略
当向线程池提交一个任务,线程池处理逻辑如下
- 线程池判断核心线程池(corePoolSize)里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。否则进入下个流程。
- 判断工作队列(workQueue)是否已满。如果工作队列没满,则将新提交的任务存储在工作队列中。否则进入下个流程。
- 判断线程池的线程(maximumPoolSize)是否都处于工作状态。如果没有,则创建一个新的线程来执行任务。如果满了,则交给饱和策略(handler)来处理这个任务。
ThreadPoolExecutor 执行任务的逻辑示意图如下
总结
本篇主要介绍线程池 ThreadPoolExecutor 的类图和任务执行的核心逻辑,下篇重点分析其源码。
参考
- JDK 1.7 源码
- Java并发编程的艺术