什么是Future 接口

很多场景下,我们想去获取线程运行的结果,而通常使用execute方法去提交任务是无法获得结果的,这时候我们常常会改用submit方法去提交,以便获得线程运行的结果。

而submit方法返回的就是Future,一个未来对象。 使用future.get() 方法去获取线程执行结果,包括如果出现异常,也会随get方法抛出。

Future 接口的缺陷

当我们使用future.get()方法去取得线程执行结果时,要知道get方法是阻塞的,也就是说为了拿到结果,当主线程执行到get()方法,当前线程会去等待异步任务执行完成,

换言之,异步的效果在我们使用get()拿结果时,会变得无效。示例如下

public static void main(String[] args) throws Exception{
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future future = executorService.submit(()->{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("异步任务执行了");
});
future.get();
System.out.println("主线任务执行了");
}

打印结果是:异步任务执行了过后主线任务才执行。  就是因为get()在一直等待。

那么如何解决我想要拿到结果,可以对结果进行处理,又不想被阻塞呢?

CompletableFuture 使一切变得可能

JDK1.8才新加入的一个实现类CompletableFuture,实现了Future<T>CompletionStage<T>两个接口。

实际开发中,我们常常面对如下的几种场景:

1.  针对Future的完成事件,不想简单的阻塞等待,在这段时间内,我们希望可以正常继续往下执行,所以在它完成时,我们可以收到回调即可。

2. 面对Future集合来讲,这其中的每个Future结果其实很难去描述它们之间的依赖关系,而往往我们希望等待所有的Future集合都完成,然后做一些事情。

3. 在异步计算中,两个计算任务相互独立,但是任务二又依赖于任务一的结果。

如上的几种场景,单靠Future是解决不了的,而CompletableFuture则可以帮我们实现。

CompletableFuture 常见api 介绍

1、 runAsync 和 supplyAsync方法

它提供了四个方法来创建一个异步任务

public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

runAsync类似于execute方法,不支持返回值,而supplyAsync方法类似submit方法,支持返回值。也是我们的重点方法。

没有指定Executor的方法会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。

  示例

   //无返回值
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
System.out.println("runAsync无返回值");
}); future1.get(); //有返回值
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("supplyAsync有返回值");
return "111";
}); String s = future2.get();

2、 异步任务执行完时的回调方法  whenComplete 和 exceptionally

当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的任务

public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)

这些方法都是上述创建的异步任务完成后 (也可能是抛出异常后结束) 所执行的方法。

whenComplete和whenCompleteAsync方法的区别在于:前者是由上面的线程继续执行,而后者是将whenCompleteAsync的任务继续交给线程池去做决定。

exceptionally则是上面的任务执行抛出异常后,所要执行的方法。

示例

CompletableFuture.supplyAsync(()->{
int a = 10/0;
return 1;
}).whenComplete((r, e)->{
System.out.println(r);
}).exceptionally(e->{
System.out.println(e);
return 2;
});

值得注意的是:哪怕supplyAsync抛出了异常,whenComplete也会执行,意思就是,只要supplyAsync执行结束,它就会执行,不管是不是正常执行完。exceptionally只有在异常的时候才会执行。

其实,在whenComplete的参数内 e就代表异常了,判断它是否为null,就可以判断是否有异常,只不过这样的做法,我们不提倡。

whenComplete和exceptionally这两个,谁在前,谁先执行。

此类的回调方法,哪怕主线程已经执行结束,已经跳出外围的方法体,然后回调方法依然可以继续等待异步任务执行完成再触发,丝毫不受外部影响。

3、 thenApply 和 handle 方法

如果两个任务之间有依赖关系,比如B任务依赖于A任务的执行结果,那么就可以使用这两个方法

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor) public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn);
public <U> CompletionStage<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn,Executor executor);

这两个方法,效果是一样的,区别在于,当A任务执行出现异常时,thenApply方法不会执行,而handle 方法一样会去执行,因为在handle方法里,我们可以处理异常,而前者不行。

示例

    CompletableFuture.supplyAsync(()->{
return 5;
}).thenApply((r)->{
r = r + 1;
return r;
}); //出现了异常,handle方法可以拿到异常 e
CompletableFuture.supplyAsync(()->{
int i = 10/0;
return 5;
}).handle((r, e)->{
System.out.println(e);
r = r + 1;
return r;
});

这里延伸两个方法  thenAccept 和 thenRun。其实 和上面两个方法差不多,都是等待前面一个任务执行完 再执行。区别就在于thenAccept接收前面任务的结果,且无需return。而thenRun只要前面的任务执行完成,它就执行,不关心前面的执行结果如何

如果前面的任务抛了异常,非正常结束,这两个方法是不会执行的,所以处理不了异常情况。

4、 合并操作方法  thenCombine 和 thenAcceptBoth

我们常常需要合并两个任务的结果,在对其进行统一处理,简言之,这里的回调任务需要等待两个任务都完成后再会触发。

public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);
public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor); public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action, Executor executor);

这两者的区别 在于 前者是有返回值的,后者没有(就是个消耗工作)

示例

private static void thenCombine() throws Exception {

        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(()->{
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "future1";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(()->{
return "future2";
}); CompletableFuture<String> result = future1.thenCombine(future2, (r1, r2)->{
return r1 + r2;
});
//这里的get是阻塞的,需要等上面两个任务都完成
System.out.println(result.get());
}
private static void thenAcceptBoth() throws Exception {

        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(()->{
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "future1";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(()->{
return "future2";
});
//值得注意的是,这里是不阻塞的
future1.thenAcceptBoth(future2, (r1, r2)->{
System.out.println(r1 + r2);
}); System.out.println("继续往下执行");
}

这两个方法 都不会形成阻塞。就是个回调方法。只有get()才会阻塞。

4、 allOf (重点,个人觉得用的场景很多)

  很多时候,不止存在两个异步任务,可能有几十上百个。我们需要等这些任务都完成后,再来执行相应的操作。那怎么集中监听所有任务执行结束与否呢? allOf方法可以帮我们完成。

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs);

它接收一个可变入参,既可以接收CompletableFuture单个对象,可以接收其数组对象。

结合例子说明其作用。

public static void main(String[] args) throws Exception{
long start = System.currentTimeMillis();
CompletableFutureTest test = new CompletableFutureTest();
// 结果集
List<String> list = new ArrayList<>(); List<Integer> taskList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 全流式处理转换成CompletableFuture[]
CompletableFuture[] cfs = taskList.stream()
.map(integer -> CompletableFuture.supplyAsync(() -> test.calc(integer))
.thenApply(h->Integer.toString(h))
.whenComplete((s, e) -> {
System.out.println("任务"+s+"完成!result="+s+",异常 e="+e+","+new Date());
list.add(s);
})
).toArray(CompletableFuture[]::new); CompletableFuture.allOf(cfs).join(); System.out.println("list="+list+",耗时="+(System.currentTimeMillis()-start));
} public int calc(Integer i) {
try {
if (i == 1) {
Thread.sleep(3000);//任务1耗时3秒
} else if (i == 5) {
Thread.sleep(5000);//任务5耗时5秒
} else {
Thread.sleep(1000);//其它任务耗时1秒
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return i;
}

全流式写法,综合了以上的一些方法,使用allOf集中阻塞,等待所有任务执行完成,取得结果集list。   这里有些CountDownLatch的感觉。

CompletableFuture 总结

图片出自

https://www.cnblogs.com/dennyzhangdd/p/7010972.html

本文只是简述了CompletableFuture的常用用法。日常开发基本够用,但是针对一些特殊场景,例如异常场景,取消场景,仍待研究。

java8 之CompletableFuture -- 如何构建异步应用的更多相关文章

  1. 《Java 8 in Action》Chapter 11:CompletableFuture:组合式异步编程

    某个网站的数据来自Facebook.Twitter和Google,这就需要网站与互联网上的多个Web服务通信.可是,你并不希望因为等待某些服务的响应,阻塞应用程序的运行,浪费数十亿宝贵的CPU时钟周期 ...

  2. java8的版本对组合式异步编程

    讨论了Java 8中的函数式数据处理,它可以将对集合数据的多个操作以流水线的方式组合在一起.本节继续讨论Java 8的新功能,主要是一个新的类CompletableFuture,它是对65节到83节介 ...

  3. 使用kendynet构建异步redis访问服务

    使用kendynet构建异步redis访问服务 最近开始在kendynet上开发手游服务端,游戏类型是生存挑战类的,要存储的数据结构和类型都比较简单,于是选择了用redis做存储,数据类型使用stri ...

  4. 教你如何构建异步服务器和客户端的 Kotlin 框架 Ktor

    Ktor 是一个使用 Kotlin 以最小的成本快速创建 Web 应用程序的框架. Ktor 是一个用于在连接系统(connected systems)中构建异步服务器和客户端的 Kotlin 框架. ...

  5. 搞定 CompletableFuture,并发异步编程和编写串行程序还有什么区别?你们要的多图长文

    你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it well enough ...

  6. 有了 CompletableFuture,使得异步编程没有那么难了!

    本文导读: 业务需求场景介绍 技术设计方案思考 Future 设计模式实战 CompletableFuture 模式实战 CompletableFuture 生产建议 CompletableFutur ...

  7. java8中CompletableFuture的使用介绍

    既然CompletableFuture类实现了CompletionStage接口,首先我们需要理解这个接口的契约.它代表了一个特定的计算的阶段,可以同步或者异步的被完成.你可以把它看成一个计算流水线上 ...

  8. Java8系列 (七) CompletableFuture异步编程

    概述 Java8之前用 Future 处理异步请求, 当你需要获取任务结果时, 通常的做法是调用  get(long timeout, TimeUnit unit) 此方法会阻塞当前的线程, 如果任务 ...

  9. Java8新特性: CompletableFuture详解

    CompletableFuture实现了CompletionStage接口和Future接口,前者是对后者的一个扩展,增加了异步回调.流式处理.多个Future组合处理的能力,使Java在处理多任务的 ...

随机推荐

  1. windows下的命令

    1.cd 现在默认只能在当前盘符中改变目录,如果要改变盘符则需要多加一个/d命令. cd /d d:/git/springTest 2.chdir 显示当前目录名或改变当前目录. CHDIR [/D] ...

  2. WPF学习笔记(8):DataGrid单元格数字为空时避免验证问题的解决

    原文:WPF学习笔记(8):DataGrid单元格数字为空时避免验证问题的解决 如下图,在凭证编辑窗体中,有的单元格不需要数字,但如果录入数字后再删除,会触发数字验证,单元格显示红色框线,导致不能执行 ...

  3. 连续小波变换CWT(2)

    如果让你说说连续小波变换最大的特点是什么?多分辨分析肯定是标准答案.所谓多分辨分析即是指小波在不同频率段会有不同的分辨率.具体表现形式,我们回到前一篇文章的第一个图, 图一 对应的信号为 低频时(频率 ...

  4. Android事件分发机制浅析(3)

    本文来自网易云社区 作者:孙有军 我们只看最重要的部分 1: 事件为ACTION_DOWN时,执行了cancelAndClearTouchTargets函数,该函数主要清除上一次点击传递的路径,之后执 ...

  5. 【3Sum Closest 】cpp

    题目: Given an array S of n integers, find three integers in S such that the sum is closest to a given ...

  6. java 二叉树递归遍历算法

    //递归中序遍历 public void inorder() { System.out.print("binaryTree递归中序遍历:"); inorderTraverseRec ...

  7. idea热部署设置(复制)

    提出问题 IntelliJ IDEA工具如何设置热部署??? 解决问题 我的IDEA的版本是:IntelliJ IDEA 14.0.2 第一步:打开tomcat配置 这里写图片描述 第二步: 这里写图 ...

  8. FTB操作

    一些参考案例下面是一些常用的ftp操作文件的方法 案列1 using System; using System.Collections.Generic; using System.Text; usin ...

  9. inner join和left join 、right join 的区别?

    left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录 right join(右联接) 返回包括右表中的所有记录和左表中联结字段相等的记录inner join(等值连接) 只 ...

  10. Linux服务器重启后nvidia-smi无法使用的解决方法

    服务器上的nvidia显卡驱动用的好好的,突然有一天,服务器断电了,然后恢复之后发现常用的nvidia-smi命令无法使用了,具体显示什么无法建立和驱动器的通信之类的,上网查了一堆,发现问题的核心:l ...