Java 异步任务执行服务 基本概念和原理

这里写目录标题

18.1 基本概念和原理

      下面,我们来看异步任务执行服务的基本接口、用法和实现原理。

18.1.1 基本接口

      首先,我们来看任务执行服务涉及的基本接口:

      ·RunnableCallable:表示要执行的异步任务。

      ·ExecutorExecutorService:表示执行服务。

      ·Future:表示异步任务的结果。

      关于Runnable和Callable,我们在前面章节都已经了解了,都表示任务,Runnable没有返回结果,而Callable有,Runnable不会抛出异常,而 Callable会。

      Executor表示最简单的执行服务,其定义为:

Java 异步任务执行服务 基本概念和原理

      就是可以执行一个Runnable,没有返回结果。接口没有限定任务如何执行,可能是创建一个新线程,可能是复用线程池中的某个线程,也可能是在调用者线程中执行。

      ExecutorService扩展了Executor,定义了更多服务,基本方法有:

Java 异步任务执行服务 基本概念和原理

      这三个submit都表示提交一个任务,返回值类型都是Future,返回后,只是表示任务已提交,不代表已执行,通过Future可以查询异步任务的状态、获取最终结果、取消任务等。我们知道,对于Callable,任务最终有个返回值,而对于Runnable是没有返回值的;第二个提交 Runnable的方法可以同时提供一个结果,在异步任务结束时返回;第三 个方法异步任务的最终返回值为null

      我们来看Future接口的定义:

Java 异步任务执行服务 基本概念和原理

      get用于返回异步任务最终的结果,如果任务还未执行完成,会阻塞等待,另一个get方法可以限定阻塞等待的时间,如果超时任务还未 结束,会抛出TimeoutException

      cancel用于取消异步任务,如果任务已完成、或已经取消、或由于某种原因不能取消,cancel返回false,否则返回true。如果任务还未开始,则不再运行。但如果任务已经在运行,则不一定能取消,参数 mayInterruptIfRunning表示,如果任务正在执行,是否调用interrupt方法 中断线程,如果为false就不会,如果为true,就会尝试中断线程,但我 们从15.4节知道,中断不一定能取消线程。

      isDoneisCancelled用于查询任务状态。isCancelled表示任务是否被取消,只要cancel方法返回了true,随后的isCancelled方法都会返回 true,即使执行任务的线程还未真正结束。isDone表示任务是否结束, 不管什么原因都算,可能是任务正常结束,可能是任务抛出了异常,也可能是任务被取消。

      我们再来看下get方法,任务最终大概有三种结果:

      1)正常完成,get方法会返回其执行结果,如果任务是Runnable且没有提供结果,返回null

      2)任务执行抛出了异常,get方法会将异常包装为 ExecutionException重新抛出,通过异常的getCause方法可以获取原异常。

      3)任务被取消了,get方法会抛出异常CancellationException

      如果调用get方法的线程被中断了,get方法会抛出 InterruptedException

      Future是一个重要的概念,是实现“任务的提交”与“任务的执行”相分离的关键,是其中的“纽带”,任务提交者和任务执行服务通过它隔离各自的关注点,同时进行协作。

18.1.2 基本用法

      说了这么多接口,具体怎么用呢?我们看个简单的例子,如代码清 单18-1所示。

Java 异步任务执行服务 基本概念和原理

      我们使用工厂类Executors创建了一个任务执行服务。Executors有多个静态方法,可以用来创建ExecutorService,这里使用的是:

Java 异步任务执行服务 基本概念和原理
      表示使用一个线程执行所有服务,后续我们会详细介绍Executors, 注意与Executor相区别,后者是单数,是接口

      不管ExecutorService是如何创建的,对使用者而言,用法都一样, 例子提交了一个任务,提交后,可以继续执行其他事情,随后可以通过 Future获取最终结果或处理任务执行的异常。

      最后,我们调用了ExecutorServiceshutdown方法,它会关闭任务执行服务

      前面我们只是介绍了ExecutorService的三个submit方法,其实它还有如下方法:
Java 异步任务执行服务 基本概念和原理
      有两个关闭方法:shutdownshutdownNow。区别是,shutdown表示不再接受新任务,但已提交的任务会继续执行,即使任务还未开始执行;shutdownNow不仅不接受新任务,而且会终止已提交但尚未执行的任务,对于正在执行的任务,一般会调用线程的interrupt方法尝试中断,不过,线程可能不响应中断,shutdownNow会返回已提交但尚未执行的任务列表。

      shutdownshutdownNow不会阻塞等待,它们返回后不代表所有任务都已结束,不过isShutdown方法会返回true。调用者可以通过 awaitTermination等待所有任务结束,它可以限定等待的时间,如果超时前所有任务都结束了,即isTerminated方法返回true,则返回true,否则 返回false

      ExecutorService有两组批量提交任务的方法:invokeAllinvokeAny,它们都有两个版本,其中一个限定等待时间。

      invokeAll等待所有任务完成,返回的Future列表中,每个FutureisDone方法都返回true,不过isDonetrue不代表任务就执行成功了,可能是被取消了。invokeAll可以指定等待时间,如果超时后有的任务没完成,就会被取消。

      而对于invokeAny,只要有一个任务在限时内成功返回了,它就会 返回该任务的结果,其他任务会被取消;如果没有任务能在限时内成功 返回,抛出TimeoutException;如果限时内所有任务都结束了,但都发 生了异常,抛出ExecutionException

      使用ExecutorService,编写并发异步任务的代码就像写顺序程序一 样,不用关心线程的创建和协调,只需要提交任务、处理结果就可以 了,大大简化了开发工作。

18.1.3 基本实现原理

      了解了ExecutorService和Future的基本用法,我们来看下它们的基 本实现原理。

      ExecutorService的主要实现类是ThreadPoolExecutor,它是基于线程池实现的,关于线程池我们下节再介绍。ExecutorService有一个抽象实现类AbstractExecutorService,本节,我们简要分析其原理,并基于它实 现一个简单的ExecutorService。Future的主要实现类是FutureTask,我们 也会简要探讨其原理。

1.AbstractExecutorService

      AbstractExecutorService提供了submitinvokeAllinvokeAny的默认实现,子类需要实现其他方法。除了execute,其他方法都与执行服务的 生命周期管理有关,简化起见,我们忽略其实现,主要考虑execute。 submit/invokeAll/invokeAny最终都会调用execute,execute决定了到底如何执行任务,简化起见,我们为每个任务创建一个线程。一个完整的最 简单的ExecutorService实现类如代码清单18-2所示。

Java 异步任务执行服务 基本概念和原理
      对于前面的例子,创建ExecutorService的代码可以替换为:

Java 异步任务执行服务 基本概念和原理

      可以实现相同的效果。

      ExecutorService最基本的方法是submit,它是如何实现的呢?我们 来看AbstractExecutor-Service的代码(基于Java 7,和Java 8 实现一样):

Java 异步任务执行服务 基本概念和原理
      它调用newTaskFor生成了一个RunnableFutureRunnableFuture是一个接口,既扩展了Runnable,又扩展了Future,没有定义新方法,作为 Runnable,它表示要执行的任务,传递给execute方法进行执行,作为 Future,它又表示任务执行的异步结果。这可能令人混淆,我们来看具体代码:

Java 异步任务执行服务 基本概念和原理
      就是创建了一个FutureTask对象,FutureTask实现了RunnableFuture 接口。它是怎么实现的呢?我们接下来看(基于Java 7,Java 8 一样)。

2.FutureTask

      它有一个成员变量表示待执行的任务,声明为:
Java 异步任务执行服务 基本概念和原理
      有个整数变量state表示状态,声明为:
Java 异步任务执行服务 基本概念和原理
      取值可能为:
Java 异步任务执行服务 基本概念和原理
      有个变量表示最终的执行结果或异常,声明为:

Java 异步任务执行服务 基本概念和原理

      有个变量表示运行任务的线程

Java 异步任务执行服务 基本概念和原理
      还有个单向链表表示等待任务执行结果的线程
Java 异步任务执行服务 基本概念和原理

      FutureTask的构造方法会初始化callable和状态,如果FutureTask接 受的是一个Runnable对象,它会调用Executors.callable转换为Callable对象,如下所示:

Java 异步任务执行服务 基本概念和原理

      任务执行服务会使用一个线程执行FutureTaskrun方法。run方法 的代码为:
Java 异步任务执行服务 基本概念和原理
      其基本逻辑是:

      1)调用callablecall方法,捕获任何异常;

      2)如果正常执行完成,调用set设置结果,保存到outcome

      3)如果执行过程发生异常,调用setException设置异常,异常也是 保存到outcome,但状态不一样;

      4)setsetException除了设置结果、修改状态外,还会调用 finishCompletion,它会唤醒所有等待结果的线程。

      对于任务提交者,它通过get方法获取结果,限时get方法的代码为:

Java 异步任务执行服务 基本概念和原理

      其基本逻辑是:如果任务还未执行完毕,就等待,最后调用report 报告结果,report根据状态返回结果或抛出异常,代码为:

Java 异步任务执行服务 基本概念和原理
      cancel方法的代码为:
Java 异步任务执行服务 基本概念和原理
      其基本逻辑为:

      ·如果任务已结束或取消,返回false

      ·如果mayInterruptIfRunningtrue,调用interrupt中断线程,设置状态为INTERR-UPTED

      ·如果mayInterruptIfRunningfalse,设置状态为CANCELLED

      ·调用finishCompletion唤醒所有等待结果的线程。

18.1.4 小结

      本节介绍了Java并发包中任务执行服务的基本概念和原理,该服务体现了并发异步开发中“关注点分离”的思想,使用者只需要通过 ExecutorService提交任务,通过Future操作任务和结果即可,不需要关 注线程创建和协调的细节。

      本节主要介绍了AbstractExecutorServiceFutureTask的基本原理,实现了一个最简单的执行服务SimpleExecutorService,对每个任务创建 一个单独的线程。实际中,最经常使用的执行服务是基于线程池实现的 ThreadPoolExecutor,让我们下一节来探讨。

参考目录

绝大多数内容来自于:Java编程的逻辑 作者: 马俊昌(第18章 异步任务执行服务 18.1 基本概念和原理)

Java官方文档
https://docs.oracle.com/javase/specs/index.html

上一篇:JAVA多线程线程池,还蒙圈的过来瞅瞅吧!!


下一篇:JUC编程(六)-线程池