一、写在开头

在我们一开始讲多线程的时候,提到过异步同步的概念,这里面我们再回顾一下:

  • 同步:调用方在调用某个方法后,等待被调用方返回结果;调用方在取得被调用方的返回值后,再继续运行。调用方顺序执行,同步等待被调用方的返回值,这就是阻塞式调用;
  • 异步:调用方在调用某个方法后,直接返回,不需要等待被调用方返回结果;被调用方开启一个线程处理任务,调用方可以同时去处理其他工作。调用方和被调用方是异步的,这就是非阻塞式调用。

适应场景

同步:如果数据存在线程间的共享,或竞态条件,需要同步。如多个线程同时对同一个变量进行读和写的操作,必须等前一个请求完成,后一个请求去调用前一个请求的结果,这时候就只能采用同步方式。

异步:当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就可以使用异步,提高效率、加快程序的响应。

而我们今天探讨的话题就是Java中的异步编程。

二、Future

为了提升Java程序的响应速度,在JDK1.5时引入了JUC包,里面包含了一个接口文件:Future,这是Java中实现异步编程的开端,我们可以将Future理解为一种异步思想或者一种设计模式;当我们执行某一耗时的任务时,可以将这个耗时任务交给一个子线程去异步执行,同时我们可以干点其他事情,不用傻傻等待耗时任务执行完成。等我们的事情干完后,我们再通过 Future 类获取到耗时任务的执行结果。

它的底层也是几个很容易理解的接口方法:

// V 代表了Future执行的任务返回值的类型
public interface Future<V> {
// 取消任务执行
// 成功取消返回 true,否则返回 false
boolean cancel(boolean mayInterruptIfRunning);
// 判断任务是否被取消
boolean isCancelled();
// 判断任务是否已经执行完成
boolean isDone();
// 获取任务执行结果
V get() throws InterruptedException, ExecutionException;
// 指定时间内没有返回计算结果就抛出 TimeOutException 异常
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutExceptio
}

这些接口大致提供的服务是:我有一个任务分配给了Future,然后我可以继续去干其他的事情,然后我可以在这个过程中去看任务是否完成,也可以取消任务,一段时间后我也可以去获取到任务执行后的结果,也可以设置任务多久执行完,没执行完抛异常等。

对于Future的使用,我想大家应该并不陌生的,我们在学习线程池的时候就有涉及,看下面这个测试案例:

//这里使用Executors只是方便测试,正常使用时推荐使用ThreadPoolExecutor!
ExecutorService executorService = Executors.newFixedThreadPool(3);
Future<String> submit = executorService.submit(() -> {
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "javabuild";
});
String s = submit.get();
System.out.println(s);
executorService.shutdown();

这里我们通过executorService.submit()方法去提交一个任务,线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功,并且可以通过 Future 的 get()方法来获取返回值。

三、Future实战

经过了上面的学习了解,我们来根据案例场景进行实战使用Future,毕竟现在很多大厂除了问面试八股文之外,更多的会涉及到场景题!

场景模拟

假如你是一个12306的开发人员,为了在节假日满足大量用户的出行需要,请高效的完成:用户搜索一个目的地,推荐出所有的交通方案+酒店+耗时,并根据价格从低到高排序

拿到这种场景题的时候,我们往往需要分步处理:

  1. 根据目的地,搜索出所有的飞机、火车、客车路线,每个路线间隔30分钟;
  2. 计算出每种路线的耗时;
  3. 根据交通方案中最后一个到站点进行可用酒店匹配;
  4. 根据不同交通方案+对应的酒店价格进行最终出行总价格计算;
  5. 将所有组合的出行方案反馈给用户。

好了,分析完我们大概需要做的步骤,我们就来通过代码实现一下吧

第一步: 我们先来创建一个固定10个线程的线程池,用来处理以上每一步的任务。

//这里使用Executors只是演示,正常使用时推荐使用ThreadPoolExecutor!
ExecutorService executor = Executors.newFixedThreadPool(10);

第二步: 部分代码实例,方法就不贴了,太多太长了,大家需要对Future的用法理解即可

// 1. 根据传入的目的地查询所有出行方案,包括交通组合,价格,到站地点,出发时间,到站时间等
Future<List<TripMethods>> tripMethods = executor.submit(() -> searchMethods(searchCondition)); List<TripMethods> methods;
try {
methods = tripMethods.get();
} catch (InterruptedException | ExecutionException e) {
// 处理异常
} // 2. 对每个出行方案的最终到站点查询酒店
List<Future<List<Hotel>>> futureHotelsList = new ArrayList<>();
for (TripMethods method : methods) {
Future<List<Hotel>> futureHotels = executor.submit(() -> searchHotels(method));
futureHotelsList.add(futureHotels);
}
// 出行方案=交通方案+酒店+耗时+价格
List<Future<List<TravelPackage>>> futureTravelPackagesList = new ArrayList<>();
for (Future<List<Hotel>> futureHotels : futureHotelsList) {
List<Hotel> hotels;
try {
hotels = futureHotels.get();
} catch (InterruptedException | ExecutionException e) {
// 处理异常
} // 3. 对每个交通方案的价格和其对应的酒店价格进行求和
for (Hotel hotel : hotels) {
Future<List<TravelPackage>> futureTravelPackages = executor.submit(() -> calculatePrices(hotel));
futureTravelPackagesList.add(futureTravelPackages);
}
} List<TravelPackage> travelPackages = new ArrayList<>();
for (Future<List<TravelPackage>> futureTravelPackages : futureTravelPackagesList) {
try {
travelPackages.addAll(futureTravelPackages.get());
} catch (InterruptedException | ExecutionException e) {
// 处理异常
}
} // 4. 将所有出行方案按照价格排序
travelPackages.sort(Comparator.comparing(TravelPackage::getPrice)); // 5. 返回结果
return travelPackages;

我们在这里将每一步分任务,都作为一个future对象,处理完返回。但是这样会带来诸多问题,比如:我们调用future的get方法是阻塞操作,大大影响效率,并且在复杂的链路关系中,这种拆分式的写法,很难理清楚关联关系,先后关系等;

四、CompletableFuture 调优

在这种背景下,Java 8 时引入CompletableFuture 类,它的诞生是为了解决Future 的这些缺陷。CompletableFuture 除了提供了更为好用和强大的 Future 特性之外,还提供了函数式编程、异步任务编排组合(可以将多个异步任务串联起来,组成一个完整的链式调用)等能力。

//CompletableFuture实现了Future的接口方法,CompletionStage 接口描述了一个异步计算的阶段。很多计算可以分成多个阶段或步骤,此时可以通过它将所有步骤组合起来,形成异步计算的流水线。
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
}

在CompletableFuture类中通过CompletionStage提供了大量的接口方法,他们让CompletableFuture拥有了出色的函数式编程能力,方法太多,我们无法一一讲解,只能通过对上面测试源码进行调优时,去使用,使用到的解释一下哈。

【CompletableFuture优化代码】

CompletableFuture.supplyAsync(() -> searchMethods())  // 1. 根据传入的目的地查询所有出行方案,包括交通组合,价格,到站地点,出发时间,到站时间等
.thenCompose(methods -> { // 2. 对每个出行方案的最终到站点查询酒店
List<CompletableFuture<List<TravelPackage>>> travelPackageFutures = methods.stream()
.map(method -> CompletableFuture.supplyAsync(() -> searchHotels(method)) // 查询酒店
.thenCompose(hotels -> { // 3. 对每个交通方案的价格和其对应的酒店价格进行求和
List<CompletableFuture<TravelPackage>> packageFutures = hotels.stream()
.map(hotel -> CompletableFuture.supplyAsync(() -> new TravelPackage(method, hotel)))
.collect(Collectors.toList()); return CompletableFuture.allOf(packageFutures.toArray(new CompletableFuture[0]))
.thenApply(v -> packageFutures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList()));
}))
.collect(Collectors.toList()); return CompletableFuture.allOf(travelPackageFutures.toArray(new CompletableFuture[0]))
.thenApply(v -> travelPackageFutures.stream()
.flatMap(future -> future.join().stream())
.collect(Collectors.toList()));
})
.thenApply(travelPackages -> { // 4. 将所有出行方案按照价格排序
return travelPackages.stream()
.sorted(Comparator.comparing(TravelPackage::getPrice))
.collect(Collectors.toList());
})
.exceptionally(e -> { // 处理所有的异常
// 处理异常
return null;
});

在这里我们将整个实现都以一种函数链式调用的方式完成了,看似冗长,实则各个关系的先后非常明确,对于复杂的业务逻辑实现更加容易进行问题的排查与理解。

【解析】

1)在这段代码的开头,我们通过CompletableFuture 自带的静态工厂方法supplyAsync() 进行对象的创建,平时还可以用以new关键字或者runAsync()方法创建实例;

static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
// 使用自定义线程池(推荐)
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);
static CompletableFuture<Void> runAsync(Runnable runnable);
// 使用自定义线程池(推荐)
static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);

2)thenCompose():用 thenCompose() 按顺序链接两个 CompletableFuture 对象,实现异步的任务链。它的作用是将前一个任务的返回结果作为下一个任务的输入参数,从而形成一个依赖关系。注意:这个方法是非阻塞的,即查询酒店的操作会立即开始,而不需要等待查询交通方案的操作完成。

3)thenApply():thenApply() 方法接受一个 Function 实例,用它来处理结果;

4)allOf() :方法会等到所有的 CompletableFuture 都运行完成之后再返回;

5) 调用 join() 可以让程序等待都运行完了之后再继续执行。

6)exceptionally():这个方法用于处理CompletableFuture的异常情况,如果CompletableFuture的计算过程中抛出异常,那么这个方法会被调用。

五、总结

好了,今天就讲这么多,其实在Java中通过条用CompletableFuture实现异步编排的工作还是稍微有点难度的,大量的API支持,需要我们在一次次的实战中去熟悉,并灵活使用。推荐大家去看看京东的asyncTool这个框架,里面就大量使用了CompletableFuture。

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

实战分析Java的异步编程,并通过CompletableFuture进行高效调优的更多相关文章

  1. Linux下jetty报java.lang.OutOfMemoryError: PermGen space及Jetty内存配置调优解决方案

    Linux下的jetty报java.lang.OutOfMemoryError: PermGen space及Jetty内存配置调优解决方案问题linux的jetty下发布程序后再启动jetty服务时 ...

  2. 异步编程利器:CompletableFuture

    一.一个示例回顾Future 一些业务场景我们需要使用多线程异步执行任务,加快任务执行速度. JDK5新增了Future接口,用于描述一个异步计算的结果.虽然 Future 以及相关使用方法提供了异步 ...

  3. 2020年薪30W的Java程序员都要求熟悉JVM与性能调优!

    前言 作为Java程序员,你有没有被JVM伤害过?面试的时候是否碰到过对JVM的灵魂拷问?   一.JVM 内存区域划分 1.程序计数器(线程私有) 程序计数器(Program Counter Reg ...

  4. JAVA高级篇(四、JVM垃圾回收和调优)

    本文转自https://zhuanlan.zhihu.com/p/25539690 JVM GC(垃圾回收机制) 在学习Java GC 之前,我们需要记住一个单词:stop-the-world .它会 ...

  5. 一线大厂Java面试必问的2大类Tomcat调优

    一.前言 最近整理了 Tomcat 调优这块,基本上面试必问,于是就花了点时间去搜集一下 Tomcat 调优都调了些什么,先记录一下调优手段,更多详细的原理和实现以后用到时候再来补充记录,下面就来介绍 ...

  6. JAVA并行异步编程,线程池+FutureTask

    java 在JDK1.5中引入一个新的并发包java.util.concurrent 该包专门为java处理并发而书写. 在java中熟悉的使用多线程的方式为两种?继续Thread类,实现Runnal ...

  7. 字节首推Java成长笔记:(原理+应用+源码+调优全都有)直接复盘

    今天这篇文章我为了帮助小伙伴们快速构建Java技术栈,这份笔记包含了Java技术点的答案,面经,笔记,希望大家看完可以在短期内容快速面试复盘,达到事半功倍! 本来想将文件上传到开源网站上去,但是文件太 ...

  8. Java异步编程——深入源码分析FutureTask

    Java的异步编程是一项非常常用的多线程技术. 之前通过源码详细分析了ThreadPoolExecutor<你真的懂ThreadPoolExecutor线程池技术吗?看了源码你会有全新的认识&g ...

  9. Atitit.异步编程 java .net php python js 对照

    Atitit.异步编程 java .net php python js 的比較 1. 1.异步任务,异步模式,  APM模式,,  EAP模式, TAP 1 1.1.       APM模式: Beg ...

  10. Atitit.异步编程 java .net php python js 的比较

    Atitit.异步编程 java .net php python js 的比较 1. 1.异步任务,异步模式,  APM模式,,  EAP模式, TAP 1 1.1.       APM模式: Beg ...

随机推荐

  1. 淘宝推荐、视频搜索背后的检索技术竟是它!深度揭秘达摩院向量检索引擎Proxima

    简介: 淘宝搜索推荐.视频搜索的背后使用了什么样的检索技术?非结构化数据检索,向量检索,以及多模态检索,它们到底解决了什么问题?今天由阿里巴巴达摩院的科学家从业务问题出发,抽丝剥茧,深度揭秘达摩院内部 ...

  2. 阿里云图数据库GDB V3引擎发布,加速开启“图智”未来

    ​简介:无论是学术界还是产业界,都对图数据库有比较高的预期.Gartner发布的<2021年十大数据和分析技术趋势>中提到:"到2025年图技术在数据和分析创新中的占比将从202 ...

  3. 达摩院重要科技突破!空天数据库引擎Ganos解读

    简介: Ganos空天数据库引擎是李飞飞带领的达摩院数据库与存储实验室研发的新一代位置智能引擎,采用了平台即服务.多模融合.计算下推和云原生全新处理架构,为政府.企事业单位.泛互联网客户提供移动对象. ...

  4. 【视频特辑】提效神器!如何用Quick BI高效配置员工的用数权限

    ​简介:随着企业数字化进程逐步加速,企业所产生和积累的数据资源日益增多.每当员工的用数权限发生变动,管理员都需要进行复杂繁琐的重复性配置流程,不仅耗时耗力还容易出错. 如何能便捷地对员工用数权限进行高 ...

  5. dotnet 修复 GitHub Action 构建过程提示 NETSDK1127 错误

    本文告诉大家,如何修复 GitHub Action 构建过程提示 error NETSDK1127: The targeting pack Microsoft.WindowsDesktop.App.W ...

  6. "友链"

    欢迎来到我的友链小屋 展示本站所有友情站点,排列不分先后,均匀打乱算法随机渲染的喔!   友链信息 博客名称:麋鹿鲁哟博客网址:https://www.cnblogs.com/miluluyo/博客头 ...

  7. jqGrid--设置单元格字体颜色

    colModel: [ { name: '列名称', index: '列名称', width: 65, sortable: true, resizable: false, cellattr: addC ...

  8. k8s应用---持久化存储和StorageClass(10)

    一.简介: 在 k8s 中为什么要做持久化存储? 在 k8s 中部署的应用都是以 pod 容器的形式运行的,假如我们部署 MySQL.Redis 等数据库,需要 对这些数据库产生的数据做备份.因为 P ...

  9. M3U8下载器加嗅探浏览器

    M3U8下载器太多了,随便一抓一大把,没什么新奇的. 下载地址: https://www.zhaimaojun.cn/P/%e8%a7%86%e9%a2%91%e7%bd%91%e7%ab%99%e5 ...

  10. ansible系列(29)--ansible的Jinja2语法及应用

    目录 1. Ansible Jinja2 1.1 jinja2语法结构 1.2 jinja2中{{ }}中的运算符 1.3 jinja2中for循环和if判断示例 1.4 Jinja2管理Nginx负 ...