增强的 Future:CompletableFuture

CompletableFuture(它实现了 Future 接口) 和 Future 一样,可以作为函数调用的契约。当你向它请求获得结果,如果数据还没有准备好,请求线程就会等待,直到数据准备好后返回。

异步执行

@Test
public void testFuture() throws ExecutionException, InterruptedException {
long t1 = System.currentTimeMillis();
Future<List<Integer>> listFuture = getListAsync();
System.out.println("do something...");
Thread.sleep(2_000L);
System.out.println("getList...");
System.out.println(listFuture.get());
System.out.println("spendTime = " + (System.currentTimeMillis() - t1));
}
private Future<List<Integer>> getListAsync() {
CompletableFuture<List<Integer>> resultFuture = new CompletableFuture<>();
new Thread( () -> {
try {
Thread.sleep(3_000L);
resultFuture.complete(Lists.newArrayList(1, 2, 3));
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
return resultFuture;
}

执行结果:

do something...
getList...
[1, 2, 3]
spendTime = 3137

以上代码中,do something 指主线程在调用过 Future 的异步接口取得凭证后,即可继续向下执行,直至 getList 需要通过凭证取得异步计算结果时,再通过 get 的方式取得。

如果采用同步调用的方式,那么以上程序则需要 3 + 2 共 5s 的时间。

使用 supplyAsync 创建 CompletableFuture

CompletableFuture 提供了更轻巧的工厂方法

@Test
public void testFuture() throws ExecutionException, InterruptedException {
long t1 = System.currentTimeMillis();
// Future<List<Integer>> listFuture = getListAsync();
CompletableFuture<List<Integer>> listFuture = CompletableFuture.supplyAsync(OrderThriftServiceTest::getList);
System.out.println("do something...");
Thread.sleep(2_000L);
System.out.println("getList...");
System.out.println(listFuture.get());
System.out.println("spendTime = " + (System.currentTimeMillis() - t1));
}
private static List<Integer> getList() {
try {
Thread.sleep(3_000L);
return Lists.newArrayList(1,2,3,4,5);
} catch (InterruptedException e) {
e.printStackTrace();
return Lists.newArrayList();
}
}

对两个 CompletableFuture 的整合

compose 与 combine

compose,在一个 CompletableFuture 执行完毕后,将执行结果通过 Function 传递给下一个 Future 进行处理。

combine,将两个 CompletableFuture 整合起来,无论它们是否存在依赖。它接受 BiFunction 第二参数,这个参数定义了当两个 CompletableFuture 对象执行完计算后,结果如何合并。

两者都提供了后缀为 Async 的版本,该方法会将后续的任务提交到一个线程池中。其中 composeAsync 其实意义不大,因为 compose 操作的时间取决于第一个 CompletableFuture 的执行时间,composeAsync 相较 compose 消耗更多的线程切换开销。

一点实际应用

在服务化的项目中,一个服务调用另一个服务所提供的批量接口,如果一次调用的量过大那么将耗费很长时间,通过 CompletableFuture 可以暂缓一些时间,用作做执行别的任务

更加优化的做法是将大批量请求分成若干个合理大小的小批量请求(每个服务一般都是多机部署的,这样多个请求通过负载均衡打到多台机器,达到了并行运算的效果),还是通过 CompletableFuture 的方式,最终将结果进行组合,组合的过程就可以用 combine 来进行,而不是先 get 再 addAll 这种 low 的做法。

CompletableFuture<List<Integer>> f1 = CompletableFuture.supplyAsync(OrderThriftServiceTest::getList);
CompletableFuture<List<Integer>> f2 = CompletableFuture.supplyAsync(OrderThriftServiceTest::getList);
CompletableFuture<List<Integer>> f3 = f1.thenCombineAsync(f2, (l1, l2) -> Stream.concat(l1.stream(), l2.stream()).collect(toList()));
// doSomething...
f3.get();

参考资料

[1] Java8 实战. 第 11 章

[2] Java 高并发程序设计. 6.5

Java8 中增强 Future:CompletableFuture的更多相关文章

  1. Java8 增强的Future:CompletableFuture(笔记)

    CompletableFuture是Java8新增的一个超大型工具类,为什么说她大呢?因为一方面它实现了Future接口,更重要的是,它实现了CompletionStage接口.这个接口也是Java8 ...

  2. java8中CAS的增强

    注:ifeve.com的同名文章为本人所发,此文在其基础做了些调整.转载请注明出处! 一.java8中CAS的增强 前些天,我偶然地将之前写的用来测试AtomicInteger和synchronize ...

  3. java8中CompletableFuture的使用介绍

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

  4. Java8 异步编排类CompletableFuture

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. https://www.cnblogs.com/shijiaqi1066/p/8758206 ...

  5. 谈一谈Java8的函数式编程(二) --Java8中的流

    流与集合    众所周知,日常开发与操作中涉及到集合的操作相当频繁,而java中对于集合的操作又是相当麻烦.这里你可能就有疑问了,我感觉平常开发的时候操作集合时不麻烦呀?那下面我们从一个例子说起. 计 ...

  6. Java8函数之旅 (二) --Java8中的流

    流与集合    众所周知,日常开发与操作中涉及到集合的操作相当频繁,而java中对于集合的操作又是相当麻烦.这里你可能就有疑问了,我感觉平常开发的时候操作集合时不麻烦呀?那下面我们从一个例子说起. 计 ...

  7. Java8中的Stream API

    本篇文章继续介绍Java 8的另一个新特性——Stream API.新增的Stream API与InputStream和OutputStream是完全不同的概念,Stream API是对Java中集合 ...

  8. java8中map的meger方法的使用

    java8中map有一个merge方法使用示例: /** * 打印出包含号码集的label的集合 * * @param args */ public static void main(String[] ...

  9. java8中的map和reduce

    java8中的map和reduce 标签: java8函数式mapreduce 2014-06-19 19:14 10330人阅读 评论(4) 收藏 举报  分类: java(47)  FP(2)  ...

随机推荐

  1. Linux遇到的问题

    一.基本命令 yum list installed | grep docker  查看yum安装了哪些软件 netstat -lnp|grep 8000 查看端口 netstat -plutn | g ...

  2. HDU.5215.Cycle(判环)

    题目链接 \(Description\) 给定\(n\)个点\(m\)条边的无向图,问是否存在一个长度为奇数/偶数的简单环. \(n\leq 10^5,m\leq 3\times 10^5\). \( ...

  3. C1驾考总结

    C1学车经验总结       科目1:理论考试,要点:驾考宝典上有关科目一的题全部做一遍,然后使用模拟测试,连续能够三次测试通过95分以上,说明差不多了.其中要特别注意,多去回顾几次做错过的题目.   ...

  4. LOJ6041 SAM+set+树状数组

    首先对于原串建$SAM$,我们可以发先在一个点$i$的$right$集合里的点的相似度就是$len[i]$,于是可以将$SAM$的$right$集合通过$set$来启发式合并,每次加入新的点对$(i, ...

  5. 将文件打包成apk

    Android Studio的打包成apk文件 https://blog.csdn.net/woaichimahua/article/details/54427528

  6. webstorm更改字体大小

    webstorm是一款不错的开发软件,一起来看看webstorm怎么更改字体大小. 1,打开该软件后,点击上面菜单栏的“文件”/File,找到其子菜单中的“设置”/Setting,点击打开. 2,在新 ...

  7. Linux中ls -l(ll)返回结果中的文件访问权限-rw-r--rw-

    linux文件访问权限(像rw-r--rw-是什么意思)   Linux的文件访问权限分为 读.写.执行三种 r:可读(4) w:可写(2)对目录来说则可新建文件 x:可执行(1)对目录来说则可进入该 ...

  8. 预编译头文件来自编译器的早期版本,或者预编译头为 C++ 而在 C 中使用它(或相反)(转)

    用VC++ 2008 编写C语言程序,编译出现错误: 预编译头文件来自编译器的早期版本,或者预编译头为 C++ 而在 C 中使用它(或相反) 解决方法: 建工程时 建立空项目 或者在项目设置里关闭预编 ...

  9. 在C#中对枚举进行位运算--枚举组合

    由于枚举的基础类型类型为基本的数值类型,支持位运算,因此可以使用一个值表示多个枚举的组合,在定义枚举时需要指定枚举数为2的幂指数方便进行位运算,即枚举数为1,2,4,8…,或1,1<<1, ...

  10. ios webview下纯JS实现长按

    app,其中有长按LI列表弹出菜单,只要清楚五个方法就行:ontouchstart.ontouchmove.ontouchend.setTimeout.clearTimeout 1.首先在我们按下手指 ...