引入
在上一篇Callable 和 Runnable 的不同?的最后,我们有提到和 Callable 配合的有一个 Future 类,通过 Future 可以了解任务执行情况,或者取消任务的执行,还可获取任务执行的结果,这些功能都是 Runnable 做不到的,Callable 的功能要比 Runnable 强大。
今天我们就来看看这个 Future 是什么。
Future 的作用
Future 最主要的作用是,比如当做一定运算的时候,运算过程可能比较耗时,有时会去查数据库,或是繁重的计算,比如压缩、加密等,在这种情况下,如果我们一直在原地等待方法返回,显然是不明智的,整体程序的运行效率会大大降低。我们可以把运算的过程放到子线程去执行,再通过 Future 去控制子线程执行的计算过程,最后获取到计算结果。这样一来就可以把整个程序的运行效率提高,是一种异步的思想。
Callable 和 Future 的关系
接下来我们介绍下 Callable 和 Future 的关系,前面讲过,Callable 接口相比于 Runnable 的一大优势是可以有返回结果,那这个返回结果怎么获取呢?就可以用 Future 类的 get 方法来获取 。因此,Future 相当于一个存储器,它存储了 Callable 的 call 方法的任务结果。除此之外,我们还可以通过Future 的 isDone 方法来判断任务是否已经执行完毕了,还可以通过 cancel 方法取消这个任务,或限时获取任务的结果等,总之 Future 的功能比较丰富。有了这样一个从宏观上的概念之后,我们就来具体看一下 Future 类的源码和使用。
Future源码
还是老样子,先看看它的源码注释:
A Future represents the result of an asynchronous computation. Methods are provided to check if the computation is complete, to wait for its completion, and to retrieve the result of the computation. The result can only be retrieved using method get when the computation has completed, blocking if necessary until it is ready. Cancellation is performed by the cancel method. Additional methods are provided to determine if the task completed normally or was cancelled. Once a computation has completed, the computation cannot be cancelled. If you would like to use a Future for the sake of cancellability but not provide a usable result, you can declare types of the form Future<?> and return null as a result of the underlying task.
Sample Usage (Note that the following classes are all made-up.)interface ArchiveSearcher { String search(String target); } class App {ExecutorService executor = ...ArchiveSearcher searcher = ...void showSearch(final String target)throws InterruptedException {Future<String> future= executor. submit(new Callable<String>() {public String call() {return searcher. search(target);}});displayOtherThings(); // do other things while searchingtry {displayText(future. get()); // use future} catch (ExecutionException ex) {cleanup();return;}} }
The FutureTask class is an implementation of Future that implements Runnable, and so may be executed by an Executor. For example, the above construction with submit could be replaced by:
FutureTask<String> future = new FutureTask<String>(new Callable<String>() { public String call() { return searcher. search(target); }}); executor.execute(future);
Memory consistency effects: Actions taken by the asynchronous computation happen-before actions following the corresponding Future. get() in another thread.
翻译:
Future 代表异步计算的结果。它提供了一些方法,用于检查计算是否完成、等待计算完成以及检索计算结果。只有在计算完成后,才能使用 get 方法检索结果,如果必要,该方法会阻塞直到结果可用。取消操作通过 cancel 方法执行。还提供了其他方法来判断任务是正常完成还是被取消。一旦计算完成,就无法再取消该计算。如果你希望为了可取消性而使用 Future,但又不提供可用的结果,可以声明 Future<?> 形式的类型,并将 null 作为底层任务的结果返回。
示例用法(注意,以下类均为虚构)/*** 定义一个归档搜索器接口,包含一个搜索方法。*/ interface ArchiveSearcher {/*** 在归档中搜索指定的目标字符串。* * @param target 要搜索的目标字符串* @return 搜索结果*/String search(String target); }/*** 应用程序类,包含执行搜索和显示结果的方法。*/ class App {// 线程池执行器,使用省略号表示具体实现ExecutorService executor = ...// 归档搜索器实例,使用省略号表示具体实现ArchiveSearcher searcher = .../*** 显示搜索结果的方法。* * @param target 要搜索的目标字符串* @throws InterruptedException 如果在等待搜索结果时线程被中断*/void showSearch(final String target)throws InterruptedException {// 提交一个 Callable 任务到线程池执行,并返回一个 Future 对象Future<String> future = executor.submit(// 创建一个 Callable 任务,用于执行搜索操作new Callable<String>() {/*** 执行搜索操作并返回结果。* * @return 搜索结果*/public String call() {// 调用搜索器的搜索方法,传入目标字符串return searcher.search(target);}});// 在搜索进行的同时,显示其他内容displayOtherThings(); try {// 获取搜索结果并显示displayText(future.get()); } catch (ExecutionException ex) {// 处理执行异常,进行清理操作cleanup();return;}} }
FutureTask 类是 Future 的一个实现,它实现了 Runnable 接口,因此可以由执行器(Executor)执行。例如,上述使用 submit 的构建方式可以替换为:
/*** 创建一个 FutureTask 对象,用于异步执行搜索任务。* FutureTask 是一个可取消的异步计算任务,它实现了 RunnableFuture 接口,* 可以作为一个任务提交给 Executor 执行,并可以获取任务的执行结果。** @param new Callable<String>() { ... } 一个实现了 Callable 接口的匿名类,* 用于定义搜索任务的具体逻辑。* Callable 接口的 call 方法返回一个结果,* 这里的结果是搜索的目标字符串。*/ FutureTask<String> future = new FutureTask<String>(// 定义一个 Callable 任务,该任务会调用 searcher 的 search 方法进行搜索new Callable<String>() {/*** 执行搜索任务并返回结果。** @return 搜索的结果字符串。*/public String call() {// 调用 searcher 的 search 方法,传入目标字符串进行搜索return searcher. search(target);}}); // 将 FutureTask 提交给 ExecutorService 执行 executor.execute(future);
内存一致性影响:异步计算所执行的操作,在另一个线程中对应 Future.get () 之后的操作之前发生。
对应源码代码如下:
/*** 表示异步计算的结果。提供了检查计算是否完成、等待计算完成以及检索计算结果的方法。* 只有在计算完成后才能使用{@code get}方法检索结果,如果需要,会阻塞直到结果准备好。* 可以使用{@code cancel}方法取消计算。还提供了其他方法来确定任务是正常完成还是被取消。* 一旦计算完成,就不能再取消。如果只是为了可取消性而使用{@code Future},* 而不提供可用的结果,可以声明{@code Future<?>}类型,并让底层任务返回{@code null}。** @param <V> 此Future的{@code get}方法返回的结果类型*/
public interface Future<V> {/*** 尝试取消此任务的执行。如果任务已经完成、已经被取消,或者由于其他原因无法取消,* 则此尝试将失败。如果成功,并且在调用{@code cancel}时任务尚未开始,* 则此任务不应再运行。如果任务已经开始,则{@code mayInterruptIfRunning}参数* 决定是否应中断执行此任务的线程以尝试停止任务。** <p>此方法返回后,后续调用{@link #isDone}将始终返回{@code true}。* 如果此方法返回{@code true},则后续调用{@link #isCancelled}将始终返回{@code true}。** @param mayInterruptIfRunning 如果执行此任务的线程应该被中断,则为{@code true};* 否则,允许正在进行的任务完成* @return 如果任务无法取消(通常是因为它已经正常完成),则返回{@code false};* 否则返回{@code true}*/boolean cancel(boolean mayInterruptIfRunning);/*** 如果此任务在正常完成之前被取消,则返回{@code true}。** @return 如果此任务在正常完成之前被取消,则返回{@code true}*/boolean isCancelled();/*** 如果此任务已完成,则返回{@code true}。** 完成可能是由于正常终止、异常或取消——在所有这些情况下,此方法都将返回{@code true}。** @return 如果此任务已完成,则返回{@code true}*/boolean isDone();/*** 如有必要,等待计算完成,然后检索其结果。** @return 计算结果* @throws CancellationException 如果计算被取消* @throws ExecutionException 如果计算抛出异常* @throws InterruptedException 如果当前线程在等待时被中断*/V get() throws InterruptedException, ExecutionException;/*** 如有必要,最多等待给定的时间以等待计算完成,然后检索其结果(如果可用)。** @param timeout 最长等待时间* @param unit 超时参数的时间单位* @return 计算结果* @throws CancellationException 如果计算被取消* @throws ExecutionException 如果计算抛出异常* @throws InterruptedException 如果当前线程在等待时被中断* @throws TimeoutException 如果等待超时*/V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}
Future 的方法和用法
可以看到Future 接口的代码,一共有 5 个方法,其中第 5 个方法是对第 4 个方法的重载,方法名一样,但是参数不一样。
get() 方法:获取结果
get 方法最主要的作用就是获取任务执行的结果,该方法在执行时的行为取决于 Callable 任务的状态,可能会发生以下 5 种情况。
- 最常见的就是当执行 get 的时候,任务已经执行完毕了,可以立刻返回,获取到任务执行的结果。
- 任务还没有结果,这是有可能的,比如我们往线程池中放一个任务,线程池中可能积压了很多任务,还没轮到我去执行的时候,就去 get 了,在这种情况下,相当于任务还没开始;还有一种情况是任务正在执行中,但是执行过程比较长,所以我去 get 的时候,它依然在执行的过程中。无论是任务还没开始或在进行中,我们去调用 get 的时候,都会把当前的线程阻塞,直到任务完成再把结果返回回来。
- 任务执行过程中抛出异常,一旦这样,我们再去调用 get 的时候,就会抛出ExecutionException异常,不管我们执行 call 方法时里面抛出的异常类型是什么,在执行 get 方法时所获得的异常都是ExecutionException。
- 任务被取消了,如果任务被取消,我们用 get 方法去获取结果时则会抛出CancellationException。
- 任务超时,我们知道 get 方法有一个重载方法,那就是带延迟参数的,调用了这个带延迟参数的get 方法之后,如果 call 方法在规定时间内正常顺利完成了任务,那么 get 会正常返回;但是如果到达了指定时间依然没有完成任务,get 方法则会抛出 TimeoutException,代表超时了。
我们来看一个代码示例:
/*** OneFuture 类演示了如何使用 Java 的 Future 和 Callable 接口来异步执行任务并获取结果。* 该类创建一个固定大小的线程池,提交一个 Callable 任务,并等待任务完成以获取结果。*/
public class OneFuture {/*** 程序的入口点。* 创建一个固定大小的线程池,提交一个 Callable 任务,并打印任务的结果。* 最后关闭线程池。** @param args 命令行参数*/public static void main(String[] args) {// 创建一个固定大小为 10 的线程池ExecutorService service = Executors.newFixedThreadPool(10);// 向线程池提交一个 Callable 任务,并返回一个 Future 对象Future<Integer> future = service.submit(new CallableTask());try {// 等待任务完成并获取结果System.out.println(future.get());} catch (InterruptedException e) {// 当线程在等待结果时被中断,打印异常堆栈信息e.printStackTrace();} catch (ExecutionException e) {// 当任务执行过程中抛出异常时,打印异常堆栈信息e.printStackTrace();}// 关闭线程池,不再接受新任务service.shutdown();}/*** CallableTask 类实现了 Callable 接口,用于在一个单独的线程中执行任务。* 该任务会休眠 3 秒钟,然后返回一个随机整数。*/static class CallableTask implements Callable<Integer> {/*** 执行任务的方法。* 线程休眠 3 秒后,返回一个随机整数。** @return 一个随机整数* @throws Exception 如果线程在休眠过程中被中断*/@Overridepublic Integer call() throws Exception {// 线程休眠 3 秒Thread.sleep(3000);// 返回一个随机整数return new Random().nextInt();}}
}
在这段代码中,main 方法新建了一个 10 个线程的线程池,并且用 submit 方法把一个任务提交进去。这个任务如代码的最下方所示,它实现了 Callable 接口,它所做的内容就是先休眠三秒钟,然后返回一个随机数。接下来我们就直接把 future.get 结果打印出来,其结果是正常打印出一个随机数,比如100192 等。这也是 Future 最常用的一种用法。
isDone() 方法:判断是否执行完毕
下面我们再接着看看 Future 的一些其他方法,比如说 isDone() 方法,该方法是用来判断当前这个任务是否执行完毕了。
需要注意的是,这个方法如果返回 true 则代表执行完成了;如果返回 false 则代表还没完成。但这里如果返回 true,并不代表这个任务是成功执行的,比如说任务执行到一半抛出了异常。那么在这种情况下,对于这个 isDone 方法而言,它其实也是会返回 true 的,因为对它来说,虽然有异常发生了,但是这个任务在未来也不会再被执行,它确实已经执行完毕了。所以 isDone 方法在返回 true 的时候,不代表这个任务是成功执行的,只代表它执行完毕了。
我们用一个代码示例来看一看,代码如下所示:
/*** GetException 类演示了如何使用 ExecutorService 提交 Callable 任务,并处理任务抛出的异常。*/
public class GetException {/*** main 方法是程序的入口点,创建一个固定大小的线程池,提交一个 Callable 任务,并处理任务执行过程中可能抛出的异常。** @param args 命令行参数*/public static void main(String[] args) {// 创建一个固定大小为 20 的线程池ExecutorService service = Executors.newFixedThreadPool(20);// 向线程池提交一个 Callable 任务,并获取一个 Future 对象Future<Integer> future = service.submit(new CallableTask());try {// 循环 5 次,每次打印当前循环次数,并休眠 500 毫秒for (int i = 0; i < 5; i++) {System.out.println(i);Thread.sleep(500);}// 打印任务是否完成的状态System.out.println(future.isDone());// 获取任务的执行结果,如果任务抛出异常,会在这一步抛出 ExecutionExceptionfuture.get();} catch (InterruptedException e) {// 处理线程被中断的异常e.printStackTrace();} catch (ExecutionException e) {// 处理任务执行过程中抛出的异常e.printStackTrace();}}/*** CallableTask 类实现了 Callable 接口,用于在一个单独的线程中执行任务。* 该任务会抛出一个 IllegalArgumentException 异常。*/static class CallableTask implements Callable<Integer> {/*** call 方法是 Callable 接口的实现,该方法会抛出一个 IllegalArgumentException 异常。** @return 由于方法会抛出异常,不会返回任何值* @throws Exception 抛出 IllegalArgumentException 异常*/@Overridepublic Integer call() throws Exception {// 抛出一个 IllegalArgumentException 异常throw new IllegalArgumentException("Callable抛出异常");}}
}
在这段代码中,可以看到有一个线程池,并且往线程池中去提交任务,这个任务会直接抛出一个异常。那么接下来我们就用一个 for 循环去休眠,同时让它慢慢打印出 0 ~ 4 这 5 个数字,这样做的目的是起到了一定的延迟作用。在这个执行完毕之后,再去调用 isDone() 方法,并且把这个结果打印出来,然后再去调用 future.get()。
这段代码的执行结果是这样的:
这里要注意,我们知道这个异常实际上是在任务刚被执行的时候就抛出了,因为我们的计算任务中是没有其他逻辑的,只有抛出异常。我们再来看,控制台是什么时候打印出异常的呢?它是在 true 打印完毕后才打印出异常信息的,也就是说,在调用 get 方法时打印出的异常。
这段代码证明了三件事情:
- 第一件事情,即便任务抛出异常,isDone 方法依然会返回 true;
- 第二件事情,虽然抛出的异常是 IllegalArgumentException,但是对于 get 而言,它抛出的异常依然是ExecutionException;
- 第三个事情,虽然在任务执行一开始时就抛出了异常,但是真正要等到我们执行get 的时候,才看到了异常。
cancel 方法:取消任务的执行
下面我们再来看一下 cancel 方法,如果不想执行某个任务了,则可以使用 cancel 方法,会有以下三种情况:
- 第一种情况最简单,那就是当任务还没有开始执行时,一旦调用 cancel,这个任务就会被正常取消,未来也不会被执行,那么 cancel 方法返回 true。
- 第二种情况也比较简单。如果任务已经完成,或者之前已经被取消过了,那么执行 cancel 方法则代表取消失败,返回 false。因为任务无论是已完成还是已经被取消过了,都不能再被取消了。
- 第三种情况比较特殊,就是这个任务正在执行,这个时候执行 cancel 方法是不会直接取消这个任务的,而是会根据我们传入的参数做判断。cancel 方法是必须传入一个参数,该参数叫作mayInterruptIfRunning,它是什么含义呢?如果传入的参数是 true,执行任务的线程就会收到一个中断的信号,正在执行的任务可能会有一些处理中断的逻辑,进而停止,这个比较好理解。如果传入的是 false 则就代表不中断正在运行的任务,也就是说,本次 cancel 不会有任何效果,同时 cancel 方法会返回 false。
那么如何选择传入 true 还是 false 呢?
传入 true 适用的情况是,明确知道这个任务能够处理中断。
传入 false 适用于什么情况呢?
- 如果我们明确知道这个线程不能处理中断,那应该传入 false。
- 我们不知道这个任务是否支持取消(是否能响应中断),因为在大多数情况下代码是多人协作的,对于这个任务是否支持中断,我们不一定有十足的把握,那么在这种情况下也应该传入 false。
- 如果这个任务一旦开始运行,我们就希望它完全的执行完毕。在这种情况下,也应该传入 false。
这就是传入 true 和 false 的不同含义和选择方法。
isCancelled() 方法:判断是否被取消
最后一个方法是 isCancelled 方法,判断是否被取消,它和 cancel 方法配合使用,比较简单。
以上就是关于 Future 的主要方法的介绍了。
用 FutureTask 来创建 Future
除了用线程池的 submit 方法会返回一个 future 对象之外,同样还可以用 FutureTask 来获取 Future 类和任务的结果。
FutureTask 首先是一个任务(Task),然后具有 Future 接口的语义,因为它可以在将来(Future)得到执行的结果。
我们来看一下 FutureTask 的源码注释:
A cancellable asynchronous computation. This class provides a base implementation of Future, with methods to start and cancel a computation, query to see if the computation is complete, and retrieve the result of the computation. The result can only be retrieved when the computation has completed; the get methods will block if the computation has not yet completed. Once the computation has completed, the computation cannot be restarted or cancelled (unless the computation is invoked using runAndReset).
A FutureTask can be used to wrap a Callable or Runnable object. Because FutureTask implements Runnable, a FutureTask can be submitted to an Executor for execution.
In addition to serving as a standalone class, this class provides protected functionality that may be useful when creating customized task classes.翻译:
一个可取消的异步计算。此类提供了Future的基本实现,包含启动和取消计算、查询计算是否完成以及检索计算结果的方法。只有在计算完成后才能检索结果;如果计算尚未完成,get方法将阻塞。一旦计算完成,除非使用runAndReset调用计算,否则无法重新启动或取消该计算。
FutureTask可用于包装Callable或Runnable对象。由于FutureTask实现了Runnable接口,因此FutureTask可以提交给Executor执行。
除了作为一个独立的类,此类还提供了一些受保护的功能,在创建自定义任务类时可能会很有用。
其代码实现:
public class FutureTask<V> implements RunnableFuture<V> {
... ...
}
可以看到,它实现了一个接口,这个接口叫作 RunnableFuture。我们再来看一下 RunnableFuture 接口的代码实现:
/*** 一个继承了 {@link Runnable} 和 {@link Future} 接口的接口。* 它表示一个可运行的未来任务,当成功执行 {@code run} 方法时,* 会导致这个 {@code Future} 任务完成,并且可以通过它的方法获取执行结果。** @param <V> 此 Future 的 {@code get} 方法返回的结果类型*/
public interface RunnableFuture<V> extends Runnable, Future<V> {/*** 执行此 Future 任务的计算。* 如果任务未被取消,则将此 Future 设置为其计算结果。* 该方法继承自 {@link Runnable} 接口,用于定义任务的执行逻辑。*/void run();
}
可以看出,它是 extends Runnable 和 Future 这两个接口的。
既然 RunnableFuture 继承了 Runnable 接口和 Future 接口,而 FutureTask 又实现了RunnableFuture 接口,所以 FutureTask 既可以作为 Runnable 被线程执行,又可以作为 Future 得到Callable 的返回值。
典型用法是,把 Callable 实例当作 FutureTask 构造函数的参数,生成 FutureTask 的对象,然后把这个对象当作一个 Runnable 对象,放到线程池中或另起线程去执行,最后还可以通过 FutureTask 获取任务执行的结果。
下面我们就用代码来演示一下:
/*** 该类演示了如何使用 FutureTask 来执行一个异步任务,并获取任务的执行结果。*/
public class FutureTaskDemo {/*** 程序的入口点,创建并启动一个 FutureTask 来执行异步任务,并打印任务的执行结果。** @param args 命令行参数*/public static void main(String[] args) {// 创建一个 Task 实例,该实例实现了 Callable 接口Task task = new Task();// 创建一个 FutureTask 实例,用于包装 Task 实例FutureTask<Integer> integerFutureTask = new FutureTask<>(task);// 创建一个新的线程,并将 FutureTask 作为任务提交给该线程执行new Thread(integerFutureTask).start();try {// 调用 FutureTask 的 get() 方法获取任务的执行结果,并打印输出System.out.println("task运行结果:"+integerFutureTask.get());} catch (InterruptedException e) {// 如果线程在等待结果时被中断,打印异常堆栈信息e.printStackTrace();} catch (ExecutionException e) {// 如果任务执行过程中抛出异常,打印异常堆栈信息e.printStackTrace();}}
}/*** 该类实现了 Callable 接口,用于定义一个可执行的任务。*/
class Task implements Callable<Integer> {/*** 该方法是 Callable 接口的实现,定义了任务的具体逻辑。** @return 任务执行的结果* @throws Exception 如果任务执行过程中发生异常*/@Overridepublic Integer call() throws Exception {// 打印信息,表示子线程正在计算System.out.println("子线程正在计算");// 初始化一个变量 sum 用于存储累加结果int sum = 0;// 使用 for 循环计算 0 到 99 的整数之和for (int i = 0; i < 100; i++) {sum += i;}// 返回计算结果return sum;}
}
在这段代码中可以看出,首先创建了一个实现了 Callable 接口的 Task,然后把这个 Task 实例传入到FutureTask 的构造函数中去,创建了一个 FutureTask 实例,并且把这个实例当作一个 Runnable 放到 new Thread() 中去执行,最后再用 FutureTask 的 get 得到结果,并打印出来。
执行结果是 4950,正是任务里 0+1+2+...+99 的结果。