这里写目录标题
18.1 基本概念和原理
下面,我们来看异步任务执行服务的基本接口、用法和实现原理。
18.1.1 基本接口
首先,我们来看任务执行服务涉及的基本接口:
·Runnable
和Callable
:表示要执行的异步任务。
·Executor
和ExecutorService
:表示执行服务。
·Future
:表示异步任务的结果。
关于Runnable和Callable,我们在前面章节都已经了解了,都表示任务,Runnable
没有返回结果,而Callable
有,Runnable
不会抛出异常,而 Callable
会。
Executor
表示最简单的执行服务,其定义为:
就是可以执行一个Runnable,没有返回结果。接口没有限定任务如何执行,可能是创建一个新线程,可能是复用线程池中的某个线程,也可能是在调用者线程中执行。
ExecutorService
扩展了Executor
,定义了更多服务,基本方法有:
这三个submit
都表示提交一个任务,返回值类型都是Future
,返回后,只是表示任务已提交,不代表已执行,通过Future
可以查询异步任务的状态、获取最终结果、取消任务等。我们知道,对于Callable
,任务最终有个返回值,而对于Runnable
是没有返回值的;第二个提交 Runnable
的方法可以同时提供一个结果,在异步任务结束时返回;第三 个方法异步任务的最终返回值为null
。
我们来看Future
接口的定义:
get
用于返回异步任务最终的结果,如果任务还未执行完成,会阻塞等待,另一个get
方法可以限定阻塞等待的时间,如果超时任务还未 结束,会抛出TimeoutException
。
cancel
用于取消异步任务,如果任务已完成、或已经取消、或由于某种原因不能取消,cancel
返回false
,否则返回true
。如果任务还未开始,则不再运行。但如果任务已经在运行,则不一定能取消,参数 mayInterruptIfRunning
表示,如果任务正在执行,是否调用interrupt
方法 中断线程,如果为false
就不会,如果为true
,就会尝试中断线程,但我 们从15.4节知道,中断不一定能取消线程。
isDone
和isCancelled
用于查询任务状态。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所示。
我们使用工厂类Executors创建了一个任务执行服务。Executors
有多个静态方法,可以用来创建ExecutorService
,这里使用的是:
表示使用一个线程执行所有服务,后续我们会详细介绍Executors, 注意与Executor相区别,后者是单数,是接口。
不管ExecutorService
是如何创建的,对使用者而言,用法都一样, 例子提交了一个任务,提交后,可以继续执行其他事情,随后可以通过 Future
获取最终结果或处理任务执行的异常。
最后,我们调用了ExecutorService
的shutdown
方法,它会关闭任务执行服务。
前面我们只是介绍了ExecutorService的三个submit方法,其实它还有如下方法:
有两个关闭方法:shutdown
和shutdownNow
。区别是,shutdown
表示不再接受新任务,但已提交的任务会继续执行,即使任务还未开始执行;shutdownNow
不仅不接受新任务,而且会终止已提交但尚未执行的任务,对于正在执行的任务,一般会调用线程的interrupt
方法尝试中断,不过,线程可能不响应中断,shutdownNow
会返回已提交但尚未执行的任务列表。
shutdown
和shutdownNow
不会阻塞等待,它们返回后不代表所有任务都已结束,不过isShutdown
方法会返回true
。调用者可以通过 awaitTermination
等待所有任务结束,它可以限定等待的时间,如果超时前所有任务都结束了,即isTerminated
方法返回true
,则返回true
,否则 返回false
。
ExecutorService
有两组批量提交任务的方法:invokeAll
和 invokeAny
,它们都有两个版本,其中一个限定等待时间。
invokeAll
等待所有任务完成,返回的Future
列表中,每个Future
的 isDone
方法都返回true
,不过isDone
为true
不代表任务就执行成功了,可能是被取消了。invokeAll
可以指定等待时间,如果超时后有的任务没完成,就会被取消。
而对于invokeAny
,只要有一个任务在限时内成功返回了,它就会 返回该任务的结果,其他任务会被取消;如果没有任务能在限时内成功 返回,抛出TimeoutException
;如果限时内所有任务都结束了,但都发 生了异常,抛出ExecutionException
。
使用ExecutorService
,编写并发异步任务的代码就像写顺序程序一 样,不用关心线程的创建和协调,只需要提交任务、处理结果就可以 了,大大简化了开发工作。
18.1.3 基本实现原理
了解了ExecutorService和Future的基本用法,我们来看下它们的基 本实现原理。
ExecutorService
的主要实现类是ThreadPoolExecutor
,它是基于线程池实现的,关于线程池我们下节再介绍。ExecutorService
有一个抽象实现类AbstractExecutorService
,本节,我们简要分析其原理,并基于它实 现一个简单的ExecutorService。Future
的主要实现类是FutureTask
,我们 也会简要探讨其原理。
1.AbstractExecutorService
AbstractExecutorService
提供了submit
、invokeAll
和invokeAny
的默认实现,子类需要实现其他方法。除了execute
,其他方法都与执行服务的 生命周期管理有关,简化起见,我们忽略其实现,主要考虑execute。 submit
/invokeAll
/invokeAny
最终都会调用execute
,execute决定了到底如何执行任务,简化起见,我们为每个任务创建一个线程。一个完整的最 简单的ExecutorService实现类如代码清单18-2所示。
对于前面的例子,创建ExecutorService的代码可以替换为:
可以实现相同的效果。
ExecutorService最基本的方法是submit,它是如何实现的呢?我们 来看AbstractExecutor-Service
的代码(基于Java 7,和Java 8 实现一样):
它调用newTaskFor
生成了一个RunnableFuture
,RunnableFuture
是一个接口,既扩展了Runnable
,又扩展了Future
,没有定义新方法,作为 Runnable
,它表示要执行的任务,传递给execute
方法进行执行,作为 Future
,它又表示任务执行的异步结果。这可能令人混淆,我们来看具体代码:
就是创建了一个FutureTask
对象,FutureTask
实现了RunnableFuture
接口。它是怎么实现的呢?我们接下来看(基于Java 7,Java 8 一样)。
2.FutureTask
它有一个成员变量表示待执行的任务,声明为:
有个整数变量state
表示状态,声明为:
取值可能为:
有个变量表示最终的执行结果或异常,声明为:
有个变量表示运行任务的线程:
还有个单向链表表示等待任务执行结果的线程:
FutureTask的构造方法会初始化callable和状态,如果FutureTask
接 受的是一个Runnable
对象,它会调用Executors.callable
转换为Callable
对象,如下所示:
任务执行服务会使用一个线程执行FutureTask
的run
方法。run方法 的代码为:
其基本逻辑是:
1)调用callable
的call
方法,捕获任何异常;
2)如果正常执行完成,调用set
设置结果,保存到outcome
;
3)如果执行过程发生异常,调用setException
设置异常,异常也是 保存到outcome
,但状态不一样;
4)set
和setException
除了设置结果、修改状态外,还会调用 finishCompletion
,它会唤醒所有等待结果的线程。
对于任务提交者,它通过get方法获取结果,限时get方法的代码为:
其基本逻辑是:如果任务还未执行完毕,就等待,最后调用report
报告结果,report
根据状态返回结果或抛出异常,代码为:
cancel
方法的代码为:
其基本逻辑为:
·如果任务已结束或取消,返回false
;
·如果mayInterruptIfRunning
为true
,调用interrupt
中断线程,设置状态为INTERR-UPTED
;
·如果mayInterruptIfRunning
为false
,设置状态为CANCELLED
;
·调用finishCompletion
唤醒所有等待结果的线程。
18.1.4 小结
本节介绍了Java并发包中任务执行服务的基本概念和原理,该服务体现了并发异步开发中“关注点分离”的思想,使用者只需要通过 ExecutorService提交任务,通过Future操作任务和结果即可,不需要关 注线程创建和协调的细节。
本节主要介绍了AbstractExecutorService
和FutureTask
的基本原理,实现了一个最简单的执行服务SimpleExecutorService,对每个任务创建 一个单独的线程。实际中,最经常使用的执行服务是基于线程池实现的 ThreadPoolExecutor,让我们下一节来探讨。
参考目录
绝大多数内容来自于:Java编程的逻辑 作者: 马俊昌(第18章 异步任务执行服务 18.1 基本概念和原理)
Java官方文档
https://docs.oracle.com/javase/specs/index.html