1.前言&目录
前言:
在上一篇文章Java源码学习之高并发编程基础——多线程的创建(上)中,讲解了Java中多线程的创建(无返回值)以及基本的运作机制,但是还有其他方式同样可以实现多线程,比如有返回值的FutureTask。
因此,本文将继续以类和接口源码解读的方式,讲解有返回值的多线程编程方式——FutureTask。
目录:
2.4.4 FutureTask#isCancelled()方法
2.源码分析
在Java源码学习之高并发编程基础——多线程的创建(上)中,详细介绍了线程的运行逻辑,本质上无论是继承Thread还是实现Runnable接口,最终的底层实现都是需要借助Thread#start()方法去调用C语言方法,通过它让JVM创建更小的执行单元去执行run()方法,这就是多线程编程的基本原理。
但是后来C语言没有实现有返回值的多线程,而是Java1.5版本以后,通过创建了几个接口、类完美的实现了获取多线程执行的返回值。这些接口和类的设计恰到好处,即充分体现了面向对象的特性,实际上也运作的很好。
这些接口和类分别是Future接口、RunnableFuture接口、Callable接口、FutureTask类,接下来将逐一分析它们的源码。
2.1 Future接口源码剖析
Future接口是一个泛型接口,其目的是为了接受多线程中不同类型的返回结果。那么它存在的意义是什么?
实际上,在Java中定义接口,是为了定义一个规范。如果你想获取多线程执行的结果那么就必须实现此接口。并且在实践中,线程池中处理有返回值的多线程也是用Future去接收返回结果的。
如下,它一共有五个抽象方法,比较常用的获取返回结果的有两个get方法,一个是没有设置超时时间的,即会一直阻塞到多线程的结果返回。另一个则是设置了超时时间,即最多会等待timeout的时间,超过时间仍然没有获取到结果则会抛出TimeoutException。
public interface Future<V> {
// 尝试取消任务执行,前提是任务处于可以被取消的状态
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();// 如果此任务在正常完成前已取消,则返回true
boolean isDone(); // 判断当前任务是否完成(包含计算中,正常完成,发生异常)
// 没有超时时间的获取方法,会一直阻塞到结果返回
V get() throws InterruptedException, ExecutionException;
// 有超时时间的获取方法,如果超时时间已过还没拿到结果则抛出异常
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
剩下三个方法,由于不同的实现类有不同的实现,因此不在这里进行深入讲解,将在后面讲解2.4 FutureTask类源码剖析时深入分析,但注意它们的返回值都是布尔类型。
2.2 RunnableFuture接口源码剖析
如果说Future接口是定义能获取多线程执行结果的规范,那么这个RunnableFuture接口的作用就是将Thread和Future衔接起来。
在Java中,多线程的执行有且仅有一种方式,就是通过调用Thread实例start()方法进而调用native的C语言方法,让其在操作系统层面创建一个更小的执行单元去执行Thread实例的run()方法。
但是无论在Thread#run()还是Runnable#()run()方法,都是无返回值的。因此在Java1.5以后,为了支持能获取多线程编程中的返回结果,定义了Future接口。为了可以接入到Thread,也定义了一个RunnableFuture的泛型接口。
如下伪代码所示,它继承了Runnable接口和Future接口。其目的就是为了即能借助Thread去实现多线程编程也能获取返回结果。
public interface RunnableFuture<V> extends Runnable, Future<V> {
// 有返回值的多线程在此方法设置返回值
void run();
}
继承Runnable接口,意味着实现RunnableFuture接口的实现类本质上也是一个Runnable实例,可以直接将其作为Thread的构造函数入参去创建一个Thread实例。
继承Future接口,意味着实现RunnableFuture接口的实现类必须重写其抽象方法,其中就包括两个获取返回的结果的get方法。
后面将要讲解的FutureTask正是实现了RunnableFuture接口,也就是说FutureTask即承担着Thread实例的成员属性(Runnable)target目标对象的角色、也拥有获取返回结果的能力。
2.3 Callable接口源码剖析
Callable接口的设计正是为了能获取多线程编程的返回结果而生,它是一个泛型接口,也就是说实现此接口的类必须明确返回结果类型。
从结构上看,它和Runnable接口非常相似,两者都是函数式接口(即可以直接使用lamada表达式去代替创建一个类并重写抽象方法)。
不同的是,Runnable#run()方法没有返回值并且也没声明异常,Callable#call()有返回值