FutureTask原理介绍——前言

238 阅读3分钟

继承结构

image.png

从FutureTask的继承结构可以看到,它既是一个Runnable,也是一个Future。

Runnable都知道,提供了一个run方法作为线程执行的入口,类似于public static void main()主函数入口是没有返回值的。因此,它可以作为Thread的构造方法入参:Thread t = new Thread(new FutureTask<String>(() -> null));

Future

Future代表了一次异步任务执行后的结果。为什么叫Future(未来),似乎名称就代表了其功能:等待某一个执行,未来拿到它的结果的意思。

接口粗略介绍

public interface Future<V> {

    // 对执行过程发送取消命令
    boolean cancel(boolean mayInterruptIfRunning);

    // 判断是否已被取消
    boolean isCancelled();

    // 判断是否执行完成
    boolean isDone();

    // 获取结果,若没执行完则阻塞;ExcutionException:执行异常后抛出
    V get() throws InterruptedException, ExecutionException;

    // 获取结果,若没执行完则限时阻塞;ExcutionException:执行异常后抛出
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

注意:这个接口本身并没有类似于run()这样的执行方法。

因此,将RunnableFuture组合起来,Runnable负责执行,Future负责拿结果,这就诞生了上述类图中的RunnableFuture,而FutureTask就是对RunnableFuture的具体实现。

疑问:这里一个地方有点疑问,在看源码时,发现FutureTaskFuture都是1.5版本发布的,而RunnableFuture到1.6才发布。

image.png

思考1🤔:

既然Runnable#run()方法本身没有返回值,Futurecancel()方法尚可以理解,就是对任务发送取消的指令,但它的get()方法,去哪里拿到结果呢?

这里就要简单介绍一下Callable了。它和Future都是1.5版本出来的,可以想到他们俩才是真正的搭档。

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

可以看到,CabllableRunnable类似,只有一个call()方法用于执行任务;不同的是,该方法一是有返回值,二是声明了异常。这恰恰与Futureget()返回值V和throw ExecutionException对应。

思考2🤔:

我们都知道,任务是依靠线程Thread#start()来执行,而start()会调用run(),而run()又会使用其成员变量的Runnable target#run()image.png 通过Thread的构造方法可以看出,参数只支持Runnable,并不支持Callable

image.png 那么Callable是如何与Thread关联起来的呢?

从上面Thread的调用逻辑,就可以学到一点。看似简单的编码背后,实则有个设计模式在背后指引,它就是委托模式Thread#run()实际是委托给Runnable#run()来执行。

基于此,我们可以自己设计一个类,对Runnable#run()再进一步委托。

public class JoJoRunnable<V> implements Runnable {

    private Callable<V> target;

    private Object result;

    public JoJoRunnable(Callable callable) {
        target = callable;
    }

    @Override
    public void run() {
        try {
            result = target.call();
        } catch (Exception e) {
            result = e;
        }
    }
}

也就是委托给Callable#call()去执行。用起来就是:

public class JoJoRunnableTest {
    public static void main(String[] args) {
       JoJoRunnable runnable = new JoJoRunnable(() -> {
           System.out.println("call");
           return "str";
       });
       new Thread(runnable).start();// 打印"call"
    }
}

时序图如下:

sequenceDiagram
Thread->>Thread: start()
Thread ->> Runnable: run()
Runnable->>Callable: call()