继承结构
从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()这样的执行方法。
因此,将Runnable和Future组合起来,Runnable负责执行,Future负责拿结果,这就诞生了上述类图中的RunnableFuture,而FutureTask就是对RunnableFuture的具体实现。
疑问:这里一个地方有点疑问,在看源码时,发现FutureTask和Future都是1.5版本发布的,而RunnableFuture到1.6才发布。
思考1🤔:
既然Runnable#run()方法本身没有返回值,Future的cancel()方法尚可以理解,就是对任务发送取消的指令,但它的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;
}
可以看到,Cabllable和Runnable类似,只有一个call()方法用于执行任务;不同的是,该方法一是有返回值,二是声明了异常。这恰恰与Future的get()的返回值V和throw ExecutionException对应。
思考2🤔:
我们都知道,任务是依靠线程Thread#start()来执行,而start()会调用run(),而run()又会使用其成员变量的Runnable target#run()。
通过
Thread的构造方法可以看出,参数只支持Runnable,并不支持Callable。
那么
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()