从 Runnable 到 FutureTask:详解 Java Executor 框架的核心组件

1.概述

Executor 作为一个灵活且强大的异步执行框架,其支持多种不同类型的任务执行策略,提供了一种标准的方法将任务的提交过程和执行过程进行了解耦开发,基于生产者和消费者模型,还提供了对生命周期的支持,以及统计信息收集,应用程序管理机制和性能检测等机制。

成员分为四个部分:任务、任务执行、任务执行结果以及任务执行工具类

  • 任务:实现Callable 接口或 Runnable 接口
  • 任务执行部分:ThreadPoolExecutor 以及 ScheduledThreadPoolExecutor
  • 任务执行结果:Future 接口以及 FutureTask 实现类
  • 任务执行工厂类:Executors

2.任务

先看看这两个接口的源码

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}
@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

这两个接口的共同点是都是用来实现多线程的接口,而它们的区别我们可以从以下三个方面来分析:

返回结果: Runnable 接口的 run() 方法没有返回值,callable 接口的 call() 方法可以返回一个结果,这个结果可以是任意类型。

异常处理: Runnable 接口的 run() 方法不能抛出异常,而Callable接口的 call() 方法可以抛出异常。当使用 Callable 时,如果任务抛出了异常,可以通过捕获Callable中的异常来处理。

使用方式上: 在使用 Runnable 时,我们通常使用 Thread 类来启动一个线程,并通过调用 Thread 的start() 方法来执行 Runnable 的 run() 方法。而在使用 Callable 时,我们必须使用ExecutorService 的 submit() 方法来提交 Callable 任务(也可以提交 Runnable 接口任务),并且它会返回一个 Future 对象

3.执行机制

3.1 Executor接口
public interface Executor {
    /**
     * 执行给定的Runnable任务.
     * 根据Executor的实现不同, 具体执行方式也不相同.
     */
    void execute(Runnable command);
} 

我们发现,作为执行机制最顶层的接口,Executor 接口就定义了一个方法,我们可以理解为这个接口把执行机制最核心的功能定义出来了——即执行任务,而后面继承它的接口,只是在增加一些附加的功能

3.2 ExecutorService

既然作为一个强大的框架,咱们总不能就只有执行任务这一个最简单的功能吧?必须得整点更丰富的功能出来,那咋办勒?于是我们弄一个新接口出来,继承 Executor 执行任务的核心功能,再增加一些功能,我们决定添加四类功能

  1. 关闭执行器,禁止任务的提交
  2. 监视执行器的状态
  3. 提供对异步任务的支持
  4. 提供对批处理任务的支持

那么咱们给这个接口起个名吧,就叫它 ExecutorService,总结一下,ExecutorService 就是在Executor 接口上增强了对任务的控制能力

3.3 ScheduledExecutorService

ExecutorService 已经对 Executor 进行了功能的增强,但是实际开发中,我们的需求是非常多的,仅仅这几个功能还远远不能满足,比如说,对于某个任务,我们希望它延时执行或者是周期执行的定时任务,所以我们还得丰富一下功能,于是 ScheduledExecutorService 接口就出现了,它提供了延时执行、周期执行的一些功能,比如说下面这个方法

     * 提交一个待执行的任务(具有返回值), 并在给定的延迟后执行该任务.
     *
     * @param command 待执行的任务
     * @param delay   延迟时间
     * @param unit    延迟时间的单位
     * @param <V>     返回值类型
     */
    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
小结
  1. Executor:提交普通的可执行任务

  2. ExecutorService:提供对线程池生命周期的管理、异步任务的支持

  3. ScheduledExecutorService:提供对任务的周期性执行支持

在这里插入图片描述

以上介绍的类, Executor ExecutorService ScheduledExecutorService 都是接口的定义,也就是上面这个图右边的三个接口,下面看一下具体的实现类

3.4 AbstractExecutorService

从下图可以看到,AbstractExecutorService 抽象类中主要是对上面所说的 ExecutorService 接口中的一些方法做了实现

在这里插入图片描述

我们要格外注意下面代码块的几个方法,因为这个实现 submit() 方法的时候,最终是 new FutureTask(runnable, value); 注意这个 FutureTask,先埋个伏笔,下文会提到这个类

   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);
    }

    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;
    }
3.5 ThreadPoolExecutor

好,咱们终于到了第一个普通类,这个类实现了 ExecutorService 的所有功能,这个类就是线程池类,非常重要,功能非常多,由于本篇文章,篇幅有限,就不展开讲这个类了,此时我们再看一下这个图,是不是就非常清晰了,至于 ScheduledThreadPoolExecutor 就不展开讲了,它就是继承了 ThreadPoolExecutor,然后把咱们上面提到的 ScheduleExecutorService 接口给实现了,再把上面那个图搬下来看一眼
在这里插入图片描述

4. 任务执行结果

4.1 Future

Future 表示异步计算的结果,提供了用于检查任务是否完成、以及获取任务结果的方法,所有方法如下图所示

  • get():等待任务完成,获取执行结果,如果任务取消会抛出异常;
  • get(long timeout, TimeUnit unit):指定等待任务完成的时间,等待超时会抛出异常;
  • isDone():判断任务是否完成;
  • isCancelled():判断任务是否被取消;
  • cancel(boolean mayInterruptIfRunning):尝试取消此任务的执行,如果任务已经完成、已经取消或由于其他原因无法取消,则此尝试将失败
4.2 FutureTask

在这里插入图片描述
上图是一张关于 FutureTask 的关系图,FutureTask 为 Future 提供了基础实现,而且又实现了 Runnable 接口,说明是可以传入任务去执行的,但是这里可能会有个疑问,Future 是表示异步计算的结果,而 Runnable 是没有返回值的,平常使用的时候 FutureTask 也都是使用 Callable,这里其实是适配器模式的体现,FutureTask 传入 实现Runnable接口任务的构造函数如下所示:

public FutureTask(Runnable runnable, V result) {
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       
}

这个构造函数会把传入的 Runnable 封装成一个 Callable 对象保存在 callable 字段中,同时如果任务执行成功的话就会返回传入的 result。这种情况下如果不需要返回值的话可以传入一个 null。

顺带看下 Executors.callable() 这个方法,这个方法的功能是把 Runnable 转换成 Callable,代码如下:

public static <T> Callable<T> callable(Runnable task, T result) {
    if (task == null)
       throw new NullPointerException();
    return new RunnableAdapter<T>(task, result);
}

可以看到这里采用的是适配器模式,调用 RunnableAdapter<T>(task, result) 方法来适配,实现如下:

static final class RunnableAdapter<T> implements Callable<T> {
    final Runnable task;
    final T result;
    RunnableAdapter(Runnable task, T result) {
        this.task = task;
        this.result = result;
    }
    public T call() {
        task.run();
        return result;
    }
}

这个适配器很简单,就是简单的实现了Callable接口,在call()实现中调用 Runnable.run() 方法,然后把传入的 result 作为任务的结果返回,还记得上面 3.4 节说的伏笔吗,那里就是 new Futuretask(),把这节关于 Futuretask() 的看完,相信你再回过去回顾一下一定要能懂了

5.Executors

Executors 提供一个简单工厂和一系列工具方法,它的所有方法都是static的,具体有哪些方法,我想从名字上,大概也能看出来,就不多啰嗦了

在这里插入图片描述

  • 36
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值