本文是自己学习Java中Future机制的笔记。阅读了很多网上的源码分析,自己对照着JDK1.8源码走了一遍。算是稍微理解了一下Future机制。
本文的内容包含如下:
为什么出现Future机制
如何使用Future机制
Future 的 UML 图
Future和FutureTask的关系,以及FutureTask的源码解析
用的知识点补充,比如Unsafe类中compareAndSwap等
一、为什么出现Future机制
常见的两种创建线程的方式。一种是直接继承Thread,另外一种就是实现Runnable接口。
这两种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。
从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。
Future模式的核心思想是能够让主线程将原来需要同步等待的这段时间用来做其他的事情。(因为可以异步获得执行结果,所以不用一直同步等待去获得执行结果)
上图简单描述了不使用Future和使用Future的区别,不使用Future模式,主线程在invoke完一些耗时逻辑之后需要等待,这个耗时逻辑在实际应用中可能是一次RPC调用,可能是一个本地IO操作等。B图表达的是使用Future模式之后,我们主线程在invoke之后可以立即返
二、Future的相关类图
2.1 Future 接口
首先,我们需要清楚,Futrue是个接口。Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
接口定义行为,我们通过上图可以看到实现Future接口的子类会具有哪些行为:
我们可以取消这个执行逻辑,如果这个逻辑已经正在执行,提供可选的参数来控制是否取消已经正在执行的逻辑。
我们可以判断执行逻辑是否已经被取消。
我们可以判断执行逻辑是否已经执行完成。
我们可以获取执行逻辑的执行结果。
我们可以允许在一定时间内去等待获取执行结果,如果超过这个时间,抛TimeoutException。
2.2 FutureTask 类
类图如下:
FutureTask是Future的具体实现。FutureTask实现了RunnableFuture接口。RunnableFuture接口又同时继承了Runnable 和 Runnable 接口。所以FutureTask既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
三、FutureTask的使用方法
举个例子,假设我们要执行一个算法,算法需要两个输入 input1 和 input2, 但是input1和input2需要经过一个非常耗时的运算才能得出。由于算法必须要两个输入都存在,才能给出输出,所以我们必须等待两个输入的产生。接下来就模仿一下这个过程。
package src;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class FutureTaskTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
long starttime = System.currentTimeMillis();
//input2生成, 需要耗费3秒
FutureTask<Integer> input2_futuretask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
Thread.sleep(3000);
return 5;
}
});
new Thread(input2_futuretask).start();
//input1生成,需要耗费2秒
FutureTask<Integer> input1_futuretask = new FutureTask<>(new Callable<Integer>() {
@Override
public Inte