Java的Future机制详解
一、为什么出现Future机制
常见的两种创建线程的方式。一种是直接继承Thread,另外一种就是实现Runnable接口。这两种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。而自从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。Future模式的核心思想是能够让主线程将原来需要同步等待的这段时间用来做其他的事情。(因为可以异步获得执行结果,所以不用一直同步等待去获得执行结果)
二、回顾Java创建线程的几种方式
三、详解异步调用
场景一:(future.get 堵塞模式)
先假设一个场景:假如你想做饭,但是没有厨具,也没有食材。而网上购买厨具比较方便,食材去超市买更放心。于是你在网上下单买厨具,但是等快递的时候不能啥也不干,可以利用这段时间去超时买菜。
这种场景在实际业务中很多:你想调用X接口,但是这个接口下游依赖其他两个接口A,B,且A、B没有依赖关系。假设A接口的执行和返回需要300ms,B接口的执行和返还需要150ms。
如果串行执行,则总共需要:300 + 150 = 450ms。而如果并行执行,则只需要 max(300,150)= 300ms。
public class FuturePractice {
// 用线程A,B的结果组成输出
static void getResult(ThreadA threadA, ThreadB threadB) {
}
// 线程A
static class ThreadA {
}
// 线程B
static class ThreadB {
}
// 假设main函数就是X接口
public static void main(String[] args) throws ExecutionException, InterruptedException {
long startTime = System.currentTimeMillis();
// 查询A接口
Callable<ThreadA> threadA = new Callable<ThreadA>() {
@Override
public ThreadA call() throws Exception {
System.out.println("异步调用线程A");
System.out.println("线程A查询中");
Thread.sleep(300);
System.out.println("线程A返回结果");
return new ThreadA();
}
};
FutureTask<ThreadA> futureA = new FutureTask<ThreadA>(threadA);
new Thread(futureA).start();
// 查询B接口
Callable<ThreadB> threadB = new Callable<ThreadB>() {
@Override
public ThreadB call() throws Exception {
System.out.println("异步调用线程B");
System.out.println("线程B查询中");
Thread.sleep(150);
System.out.println("线程B返回结果");
return new ThreadB();
}
};
FutureTask<ThreadB> futureB = new FutureTask<ThreadB>(threadB);
new Thread(futureB).start();
// 使用A、B接口的值组成返回值
if (!futureA.isDone() && !futureB.isDone()) {
System.out.println("A、B接口并未查询结束");
}
ThreadA threadAResult = futureA.get();//同步堵塞
ThreadB threadBResult = futureB.get();//同步堵塞
System.out.println("A、B接口查询完毕,返回X接口结果");
getResult(threadAResult, threadBResult);
System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
}
}
输出结果:(观察输出结果即可明白整个过程)
异步调用线程A
线程A查询中
A、B接口并未查询结束
异步调用线程B
线程B查询中
线程B返回结果
线程A返回结果
A、B接口查询完毕,返回X接口结果
总共用时305ms
场景二:(future 回调模式 效率最高)
举个通俗的例子:打电话,有一天小王遇到一个很难的问题,问题是“1 + 1 = ?”,就打电话问小李,小李一下子也不知道,就跟小王说,等我办完手上的事情,就去想想答案,小王也不会傻傻的拿着电话去等小李的答案吧,于是小王就对小李说,我还要去逛街,你知道了答案就打我电话告诉我,于是挂了电话,自己办自己的事情,过了一个小时,小李打了小王的电话,告诉他答案是2 。
跟第一个场景其实很像,你想调用X接口,但是这个接口下游依赖其他接口A(简化场景1)。且你还有其他事要做,不能干等A的结果。于是采用回调的方式,告诉A,B拿到结果后通告我一声。
回调函数例子见:一个经典例子让你彻彻底底理解java回调机制
Guava对fature的拓展:Future 异步回调 大起底之 Java Future 与 Guava Future