【JUC-04】JUC—与线程池有关的Executor框架介绍

前言:无论是学习中还是工作中线程池的类ThreadPoolExecutor经常是最常用的类,许多初学者一上来就陷入了看ThreadPoolExecutor类的源码,但是为何有这个类,该类继承了哪些类,为什么要这么设计等等,面对一堆问题都是一头雾水,本篇作者借着自己初学者的视角和大家一起分享自己的了解。
注:下文所说的Executor框架都是一种泛指,包括executor接口、ExecutorService等等,可以参见下图
在这里插入图片描述

1. 简介

Executor本意就是为了控制线程的产生和执行的逻辑,尝试着将线程的工作单元和线程的执行单元分离开来,而Executor框架主要控制着线程的执行,其他的比如实现Runnable的线程负责工作单元。

1.1 Executor框架元素构成

框架主要由以下三部分组成:

  1. 线程工作单元。被执行的任务都需要实现的接口:Runnable接口及Callable接口。前者无返回值,后者执行完成有返回值。
  2. 任务真正的执行工作类。包括上图中的最根本的父接口:Executor接口(主要任务将提交和执行任务分离开来)、继承自Executore接口的ExecutorService接口,以及实现了ExecutorService的两个类:ThreadPoolExecutor(线程池的类、主要用来执行被提交的任务)、ScheduledThreadPoolExecutore(定时线程池的类)
  3. 异步计算结果。包括Future接口和实现Future接口的FutureTask类。

具体的流转过程如下图所示:

  1. 首先主线程创建Runnable或者Callable的线程任务、当然也可以通过Executors工厂类将Runnable的线程转化为Callable的线程。
  2. 利用ExecutorService的submit或者execute方法执行任务,如果是前者会返回实现了Future的对象,后者则不返回。
  3. 主线程可以通过FutureTask.get()或者产生的结果,或者调用cacel方法取消任务的执行。

在这里插入图片描述

2. 核心类分析

2.1 Executor

该接口是Executor框架的最核心的类。该类最为最基础的类只有一个目的将线程的产生过程和线程的执行过程分类开发,该类只负责线程的提交执行,只有一个方法。

主要提交一个实现Runnable接口的线程,当然也可以提交一个Callable(需要提前由Executors进行转化)

void execute(Runnable command);

2.2 ExecutorService

该接口实现了Executor接口,因此在Executor接口基础上,提供更多的管理线程执行的方法,比如提交可以由返回值的方法、控制线程的终止,提供两种终止的方法。

1. void shutdown()

开始有序关闭以前已经提交的任务,但是不允许提交新的任务。源码中介绍需要额外注意一点,这里可能容易引起误解。
在这里插入图片描述
在调用该方法之前,之前已经提交的任务所在的线程会马上返回结果,但是正在运行的任务会交由其他线程完成。
如果想程序等待已经提交的任务执行并返回结果,可以先调用awaitTermination(long, TimeUnit),再调用shutdown()方法。

如果想粗鲁关闭所有的线程,马上终止,可以调用如下方法。

2. List shutdownNow()

关闭所有执行中任务,返回等待执行的线程。
执行完该方法后,达到的如下效果:没有活跃的线程在执行任务、没有任务等待执行、不可以提交新任务。

List<Runnable> shutdownNow();

3. isShutdown()

检查执行器是否被关闭。

/**
 * 如果执行器已经被关闭,则返回true
 * @return
 */
boolean isShutdown();

4. boolean isTerminated()

如果在关闭之前,所有的任务都已经完成,则返回true,
如果没有调用shutdown()或者shutdownNow(),该方法永远不可能返回true。

boolean isTerminated();

5. awaitTermination(long timeout, TimeUnit unit)

调用shutdown(),让所有任务执行完之前,线程都会阻塞。除非超时或者当前线程被中断。

/**
 * 调用shutdown(),让所有任务执行完之前,线程都会阻塞。
 * 除非超时或者当前线程被中断
 * @param timeout   超时时间
 * @param unit      时间单位
 * @return
 * @throws InterruptedException
 */
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;

6.提交任务的方法

1. submit(Callable task)

提交一个实现Callable的线程任务,返回任务的执行结果。
当然如果你想阻塞当前线程,等待任务的执行结果,可以提交完任务之后直接调用get()方法获取结果。
result = exec.submit(Callable).get()

/**
 * 提交一个实现Callable的线程任务,返回任务的执行结果
 * @param task  提交的任务
 * @param <T>  返回的结果集
 * @return
 */
<T> Future<T> submit(Callable<T> task);
2. submit(Runnable task, T result)

提交一个实现了Runnable的线程任务,并且将返回结果设置成result

/**
 * @param task  提交线程
 * @param result  返回的结果
 * @param <T>  结果的参数类型
 * @return
 */
<T> Future<T> submit(Runnable task, T result);
3. submit(Runnable task)

提交一个实现了Runnable接口的线程任务,返回结果以Future类型

/**
 * @param task
 * @return   任务执行完成的结果
 */
Future<?> submit(Runnable task);

7. 执行任务

1. invokeAll(Collection<? extends Callable> tasks)

执行所有提交的任务,当所有结束时返回所有的状态及结果

/**
 * @param tasks     所有任务集合
 * @param <T>       任务执行完成之后的返回类型
 * @return          结果集合
 * @throws InterruptedException
 */
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
2. invokeAll(Collection<? extends Callable> tasks, long timeout, TimeUnit unit)

执行所有提交的任务,当所有任务执行完成或者超时时,返回所有的状态及结果,没有执行完的任务将被取消。

/**
  * @param tasks         提交的任务
  * @param timeout       超时时间
  * @param unit          超时单元
  * @param <T>           任务返回值类型
  * @return
  * @throws InterruptedException
  */
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                               long timeout, TimeUnit unit) throws InterruptedException;
3. 其余的方法

其他的比如一下两种方法,如上面的方法类似。

<T> T invokeAny(Collection<? extends Callable<T>> tasks)throws InterruptedException, ExecutionException;


<T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;

8. ExecutorService接口总结

到目前为止,该方法已经主要包括任务的提交、任务的终止判断、任务的调用等核心方法。

2.3 AbstractExecutorService

1. 为什么需要该抽象类

该抽象类主要实现了ExecutorService接口,主要目的在于提供系统默认的Futrue的返回结果类型。该类主要实现submit(), invokeAny()invokeAll()等方法,利用自定义newTaskFor()方法确定了返回结果的抽象类(RunnableFuture),系统默认提供了FutureTask类。但是继承了AbstractExecutorService的子类可以重写newTaskFor()方法,定义自己的返回结果。只要返回结果是RunnableFuture的子类,而并非一定是FutureTask,这在一定程度上保证的方法的扩展性。

之前在ExecutorService接口中总结过的方法,不再赘述,只是讲解本次新增的一些方法。

2. 核心方法

1. newTaskFor(Runnable runnable, T value)

提交线程任务(实现了Runnable接口),并且给了默认的返回值value,返回系统默认的RunnableFuture接口的子类FutureTask类型的结果。
作用:规定系统默认的返回类型为FutureTask,但是该类并不是直接对外暴露,而是提供给AbstractExecutorService中的submit方法的。

/**
 * @param runnable          实现Runnable接口的线程
 * @param value             返回结果的默认值
 * @param <T>               返回值的类型
 * @return                  系统默认提供的类型返回类型为FutureTask
 */
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}
2. newTaskFor(Callable callable)

线程任务返回RunnableFuture类型的结果,无给定默认值

/**
 * @param callable              实现Callable接口的线程
 * @param <T>                   返回值类型
 * @return                      系统默认提供的类型返回类型为FutureTask
 */
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}
3. submit(Runnable task, T result)

提交任务,该方法源于父类ExecutorService,在这里进行二次加工,精确返回值类型(FutureTask),并且给了默认返回值

/**
 * @param task  提交线程
 * @param result  返回的结果
 * @param <T>
 * @return
 */
public <T> Future<T> submit(Runnable task, T result) {
    if (task == null) throw new NullPointerException();
    // newTaskFor(task, result) 调用上述方法,固定特定的返回值
    RunnableFuture<T> ftask = newTaskFor(task, result);
    execute(ftask);
    return ftask;
}

3. 总结

到目前为止Executor框架的由顶层到底层的三个核心类都已经讲解完成。Executor接口左右该框架的最顶层接口,主要功能是提供提交执行任务的接口方法,ExecutorService接口实现了Executor类,并额外提供了提交任务、终止任务、判断任务是否终止、执行任务的Invoke方法;在上述基础上定义抽象类AbstractExecutorService,该类主要的作用定义submit方法的返回值类型,框架默认提供FutureTask类型,但是用户也可以自己实现RunnableTask接口,定义自己的返回值类型。

下一讲将介绍Executor框架最核心类ThreadPoolExecutor,该类是线程池最最核心的类,并且将今天讲解的三个核心类与AQS结合起来。

4. 参考文献

  1. Oracle官方文档
  2. 《java并发编程艺术》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值