Java 是如何实现 Future 模式的
前言
1 Future是什么?
我们平时网购买东西,下单后会生成一个订单号,然后商家会根据这个订单号发货,发货后又有一个快递单号,然后快递公司就会根据这个快递单号将网购东西快递给我们。在这一过程中,这一系列的单号都是我们收货的重要凭证。
因此,JDK的Future就类似于我们网购买东西的单号,当我们执行某一耗时的任务时,我们可以另起一个线程异步去执行这个耗时的任务,同时我们可以干点其他事情。当事情干完后我们再根据future这个"单号"去提取耗时任务的执行结果即可。因此Future也是多线程中的一种应用模式。
扩展:
说起多线程,那么Future又与Thread有什么区别呢?最重要的区别就是Thread是没有返回结果的,而Future模式是有返回结果的
如何使用Future
前面搞明白了什么是Future,下面我们再来举个简单的例子看看如何使用Future。
假如现在我们要打火锅,首先我们要准备两样东西:把水烧开和准备食材。因为烧开水是一个比较漫长的过程(相当于耗时的业务逻辑),因此我们可以一边烧开水(相当于另起一个线程),一边准备火锅食材(主线程),等两者都准备好了我们就可以开始打火锅了。
// DaHuoGuo.java public class DaHuoGuo { public static void main(String[] args) throws Exception { FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() { @Override public String call() throws Exception { System.out.println(Thread.currentThread().getName() + ":" + "开始烧开水..."); // 模拟烧开水耗时 Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + ":" + "开水已经烧好了..."); return "开水"; } }); Thread thread = new Thread(futureTask); thread.start(); // do other thing System.out.println(Thread.currentThread().getName() + ":" + " 此时开启了一个线程执行future的逻辑(烧开水),此时我们可以干点别的事情(比如准备火锅食材)..."); // 模拟准备火锅食材耗时 Thread.sleep(3000); System.out.println(Thread.currentThread().getName() + ":" + "火锅食材准备好了"); String shicai = "火锅食材"; // 开水已经稍好,我们取得烧好的开水 String boilWater = futureTask.get(); System.out.println(Thread.currentThread().getName() + ":" + boilWater + "和" + shicai + "已经准备好,我们可以开始打火锅啦"); } } 执行结果如下截图

从以上代码中可以看到,我们使用Future主要有以下步骤:
1、新建一个Callable匿名函数实现类对象,我们的业务逻辑在Callable的call方法中实现,其中Callable的泛型是返回结果类型;
2、 然后把Callable匿名函数对象作为FutureTask的构造参数传入,构建一个futureTask对象;
3、 然后再把futureTask对象作为Thread构造参数传入并开启这个线程执行去执行业务逻辑;
4、 最后我们调用futureTask对象的get方法得到业务逻辑执行结果。
可以看到跟Future使用有关的JDK类主要有FutureTask和Callable两个,下面主要对FutureTask进行源码分析。
扩展:
还有一种使用Future的方式是将Callable实现类提交给线程池执行的方式,这里不再介绍,自行百度即可。
FutureTask类结构分析
类图如下

可以看到FutureTask实现了RunnableFuture接口,而RunnableFuture接口又继承了Future和Runnable接口。因为FutureTask间接实现了Runnable接口,因此可以作为任务被线程Thread执行;此外,最重要的一点就是FutureTask还间接实现了Future接口,因此还可以获得任务执行的结果。下面我们就来简单看看这几个接口的相关api。
@FunctionalInterface public interface Runnable { // 执行线程任务 public abstract void run(); } Runnable没啥好说的,相信大家都已经很熟悉了。
// Future.java public interface Future<V> { /** * 尝试取消线程任务的执行,分为以下几种情况: * 1)如果线程任务已经完成或已经被取消或其他原因不能被取消,此时会失败并返回false; * 2)如果任务还未开始执行,此时执行cancel方法,那么任务将被取消执行,此时返回true;TODO 此时对应任务状态state的哪种状态???不懂!! * 3)如果任务已经开始执行,那么mayInterruptIfRunning这个参数将决定是否取消任务的执行。 * 这里值得注意的是,cancel(true)实质并不能真正取消线程任务的执行,而是发出一个线程 * 中断的信号,一般需要结合Thread.currentThread().isInterrupted()来使用。 */ boolean cancel(boolean mayInterruptIfRunning); /** * 判断任务是否被取消,在执行任务完成前被取消,此时会返回true */ boolean isCancelled(); /** * 这个方法不管任务正常停止,异常还是任务被取消,总是返回true。 */ boolean isDone(); /** * 获取任务执行结果,注意是阻塞等待获取任务执行结果。 */ V get() throws InterruptedException, ExecutionException; /** * 获取任务执行结果,注意是阻塞等待获取任务执行结果。 * 只不过在规定的时间内未获取到结果,此时会抛出超时异常 */ V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; } Future接口象征着异步执行任务的结果即执行一个耗时任务完全可以另起一个线程执行,然后此时我们可以去做其他事情,做完其他事情我们再调用Future.get()方法获取结果即可,此时若异步任务还没结束,此时会一直阻塞等待,直到异步任务执行完获取到结果
// RunnableFuture.java public interface RunnableFuture<V> extends Runnable, Future<V> { /** * Sets this Future to the result of its computation * unless it has been cancelled. */ void run(); } RunnableFuture是Future和Runnable接口的组合,即这个接口表示又可以被线程异步执行,因为实现了Runnable接口,又可以获得线程异步任务的执行结果,因为实现了Future接口。因此解决了Runnable异步任务没有返回结果的缺陷。
接下来我们来看下FutureTask,FutureTask实现了RunnableFuture接口,因此是Future和Runnable接口的具体实现类,是一个可被取消的异步线程任务,提供了Future的基本实现,即异步任务执行后我们能够获取到异步任务的执行结果,是我们接下来分析的重中之重。FutureTask可以包装一个Callable和Runnable对象,此外,FutureTask除了可以被线程执行外,还可以被提交给线程池执行。
我们先看下FutureTask类的api,其中重点方法已经红框框出。

上图中FutureTask的run方法是被线程异步执行的方法,get方法即是取得异步任务执行结果的方法,还有cancel方法是取消任务执行的方法。接下来