本节思维导图:
一 使用线程池的好处
线程池 提供一种限制和管理资源(包括执行一个任务)。每个线程池还维护一些基本统计信息,例如已完成任务的数量。
这里借用《Java并发编程的艺术》 提到的来说一下使用线程池的好处。
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的开销。
- 提高响应速度。当任务可达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配,调优和监控。
二 Executor框架
2.1 简介
Executor 框架是Java5 之后引进的,在Java5 之后,通过Executor 来启动线程比使用 Thread 的start 方式更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免 this 逃逸问题。
补充:this逃逸 是指在构造函数返回之前,其他线程就持有该对象的饮用。调用尚未构造完全的对象方法可能引发令人疑惑的错误。
2.2 Executor 框架结构(主要由三大部分组成)
1 任务。
执行任务需要实现的 Runnable接口或Callable接口。
Runable接口或Callable接口实现类都可以被ThreadPoolExecutor或ScheduledThreadoolExecutor执行。
两者的区别:
Runnable接口不会返回结果,但是Callable接口可以返回结果。后面介绍Executor类的一些方法的时候会介绍到两者的相互转换。
2 任务的执行
如下图所示,包括认为执行机制的核心接口Executor,以及继承自Executor 接口的ExecutorService接口。ScheduledThreadPoolExecutor和ThreadPoolExecutor这两个关键类实现了ExecutorService接口。
注意:通过查看ScheduledThreadPoolExecutor源代码可以发现,ScheduledThreadPoolExecutor实际上是继承了ThreadPoolExecutor,并实现了SchduledExecutorService,而ScheduledExecutorService又实现了ExecutorService,正如下面的类图:
ThreadPoolExecutor类描述:
* @since 1.5
* @author Doug Lea
*/
public class ThreadPoolExecutor extends AbstractExecutorService {
ScheduledThreadPoolExecutor类描述:
* @since 1.5
* @author Doug Lea
*/
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService {
类关系图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UNmsuiZG-1572841549196)(C:\Users\hbing\AppData\Roaming\Typora\typora-user-images\1572594208514.png)]
3 异步计算的结果
Future接口 以及 Future接口的实现类 FutureTask类。
当我们把Runnable接口或Callable接口的实现类提交(调用submit方法)给ThreadPoolExecutor或ScheduledPoolExecutor时,会返回一个FutureTask对象。
我们以AbstractExecutorService接口中的一个submit方法为例子来看看源代码:
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
进入newTaskFor方法,看看返回值。
* @since 1.6
*/
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
返回的是一个FutureTask对象。
2.3 Executor 框架的使用示意图
-
主线程首先要创建实现Runnable或者Callable接口的任务对象。
工具类Executors 可以实现Runnable对象和Callable对象之间的相互转换。
public static Callable<Object> callable(Runnable task) { public static <T> Callable<T> callable(Runnable task, T result) {
-
然后可以把创建完成的Runnable对象直接交给ExecutorService执行
ThreadTest task = new ThreadTest(); Callable callable = Executors.callable(task); ExecutorService executorService = Executors.newFixedThreadPool(5); executorService.execute(task); Future future1 = executorService.submit(task); FutureTask future2 = (FutureTask) executorService.submit(callable); boolean isDone = future1.isDone();
执行execute()方法和submit()方法的区别是什么呢?
- execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
- **submit()方法用于提交需要返回值的任务,线程池会返回一个Future类型的对象,通过这个Future对象可以判断任务是否成功执行,**并且可以通过future的get()方法获取返回值,get()方法会阻塞当前线程直到任务完成,而get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务还没有完成。
-
如果执行ExecutorService.submit(。。。),ExecutorService将会返回一个实现Future接口对象(到目前为止的JDK中,返回的都是FutureTask对象)。由于FutureTask实现了Runnable接口,也可以创建FutureTask,然后交给ExecutorService去执行。
-
最后,主线程可以执行FutureTask.get()方法来等待任务执行完成。主线程也可以执行FutureTask.cancel(boolean mayInterruptIfRunning)来取消任务的执行。
三 ThreadPoolExecutor详解
线程池实现类ThreadPoolExecutor是Executor 框架最核心的类,来看看比较重要的四个属性:
3.1 ThreadPoolExecutor类的四个比较重要的属性
3.2 ThreadPoolExecutor类中提供的四个构造方法
我们最常看到这个方法,其余三个都是在这个构造方法的基础上产生的
/**
* 用给定的初始参数创建一个新的ThreadPoolExecutor。
* @param keepAliveTime 当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提 交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime;
* @param unit keepAliveTime参数的时间单位
*
* @param workQueue 等待队列,当任务提交时,如果线程池中的线程数量大于等于corePoolSize的时 候,把该任务封装成一个Worker对象放入等待队列ÿ