Future

JDK5 新增了Future接口,用于描述一个异步计算的结果。

虽然 Future 以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,我们必须使用Future.get()的方式阻塞调用线程,或者使用轮询方式判断 Future.isDone 任务是否结束,再获取结果。

并且,Future 无法解决多个异步任务相互依赖的场景,简单点说就是,主线程需要等待子线程任务执行完毕之后在进行执行,这个时候你可能想到了 「CountDownLatch」,没错确实可以解决,代码如下。

这里定义两个 Future,第一个通过用户 id 获取用户信息,第二个通过商品 id 获取商品信息。

public void testCountDownLatch() throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(5); CountDownLatch downLatch = new CountDownLatch(2); long startTime = System.currentTimeMillis();
Future<String> userFuture = executorService.submit(() -> {
//模拟查询商品耗时500毫秒
Thread.sleep(500);
downLatch.countDown();
return "用户A";
}); Future<String> goodsFuture = executorService.submit(() -> {
//模拟查询商品耗时500毫秒
Thread.sleep(400);
downLatch.countDown();
return "商品A";
}); downLatch.await();
//模拟主程序耗时时间
Thread.sleep(600);
System.out.println("获取用户信息:" + userFuture.get());
System.out.println("获取商品信息:" + goodsFuture.get());
System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms"); }

Java8 以后这不再是一种优雅的解决方式,接下来来了解下 CompletableFuture 的使用。

CompletableFuture

@Test
public void testCompletableInfo() throws InterruptedException, ExecutionException {
long startTime = System.currentTimeMillis(); //调用用户服务获取用户基本信息
CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() ->
//模拟查询商品耗时500毫秒
{
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "用户A";
}); //调用商品服务获取商品基本信息
CompletableFuture<String> goodsFuture = CompletableFuture.supplyAsync(() ->
//模拟查询商品耗时500毫秒
{
try {
Thread.sleep(400);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "商品A";
}); System.out.println("获取用户信息:" + userFuture.get());
System.out.println("获取商品信息:" + goodsFuture.get()); //模拟主程序耗时时间
Thread.sleep(600);
System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
}

CompletableFuture 创建方式

「supplyAsync」执行任务,支持返回值。

「runAsync」执行任务,没有返回值。

参数如果传了线程池就使用自定义的线程池,没传则使用默认内置线程池ForkJoinPool.commonPool(),根据supplier构建执行任务。(注意:默认内置线程池核心数为机器核心数减一,如果机器核心数比2小时,会创建一个新线程去跑任务,建议在高并发场景使用自定义线程池

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

CompletableFuture 获取方式

//方式一
public T get() //方式二 public T get(long timeout, TimeUnit unit) //方式三 public T getNow(T valueIfAbsent) //方式四 public T join()

说明:

「get()和 get(long timeout, TimeUnit unit)」 => 在 Future 中就已经提供了,后者提供超时处理,如果在指定时间内未获取结果将抛出超时异常

「getNow」 => 立即获取结果不阻塞,结果计算已完成将返回结果或计算过程中的异常,如果未计算完成将返回设定的 valueIfAbsent 值

「join」 => 方法里有异常不会抛出异常,但会抛出 CompletionException

异步回调方法

1、thenRun/thenRunAsync

通俗点讲就是,「做完第一个任务后,再做第二个任务,第二个任务也没有返回值」。

【Async】加了则第一个任务使用的是你自己传入的线程池,第二个任务使用的是 ForkJoin 线程池,没加则第二个线程池也用传入的线程池。

2、thenAccept/thenAcceptAsync

第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,但是回调方法是没有返回值的。

3、thenApply/thenApplyAsync

表示第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,并且回调方法是有返回值的。

异常回调

whenComplete + exceptionally 示例

public void testWhenCompleteExceptionally() throws ExecutionException, InterruptedException {
CompletableFuture<Double> future = CompletableFuture.supplyAsync(() -> {
if (Math.random() < 0.5) {
throw new RuntimeException("出错了");
}
System.out.println("正常结束");
return 0.11; }).whenComplete((aDouble, throwable) -> {
if (aDouble == null) {
System.out.println("whenComplete aDouble is null");
} else {
System.out.println("whenComplete aDouble is " + aDouble);
}
if (throwable == null) {
System.out.println("whenComplete throwable is null");
} else {
System.out.println("whenComplete throwable is " + throwable.getMessage());
}
}).exceptionally((throwable) -> {
System.out.println("exceptionally中异常:" + throwable.getMessage());
return 0.0;
}); System.out.println("最终返回的结果 = " + future.get());
}

当出现异常时,exceptionally 中会捕获该异常,给出默认返回值 0.0。

而 「whenComplete」 这个回调函数:

「正常完成」:whenComplete 返回结果和上级任务一致,异常为 null;

「出现异常」:whenComplete 返回结果为 null,异常为上级任务的异常;

结果:

whenComplete aDouble is null
whenComplete throwable is java.lang.RuntimeException: 出错了
exceptionally中异常:java.lang.RuntimeException: 出错了
最终返回的结果 = 0.0

注意点

1、Future 需要获取返回值,才能获取异常信息

Future 需要获取返回值,才能获取到异常信息。如果不加 get()/join()方法,看不到异常信息。如果想要获取,考虑是否加 try...catch...或者使用 exceptionally 方法。

2、CompletableFuture 的 get()方法是阻塞的

CompletableFuture 的 get()方法是阻塞的,如果使用它来获取异步调用的返回值,需要添加超时时间。

3、不建议使用默认线程池

CompletableFuture 代码中使用了默认的 「ForkJoin 线程池」, 处理的线程个数是电脑 「CPU 核数-1」 。在大量请求过来的时候,处理逻辑复杂的话,响应会很慢。一般建议使用自定义线程池,优化线程池配置参数。

4、自定义线程池时,注意拒绝策略

如果线程池拒绝策略是 DiscardPolicy(丢弃当前任务) 或者 DiscardOldestPolicy(丢弃最旧的那个任务),当线程池饱和时,会直接丢弃任务,不会抛弃异常。因此建议,CompletableFuture 线程池策略最好使用 AbortPolicy(抛出执行异常)或者CallerRunsPolicy(让主线程执行)。

结合业务代码使用示例

Util工具类

public class CompletableFutureUtil {

    private CompletableFutureUtil(){}

    public static <R> CompletableFuture<R>  executeWithFallbackAndContextPropagation(@Nonnull Supplier<R> normalFunction,
@Nonnull Supplier<R> exceptionFunction,
@Nonnull ThreadPoolTaskExecutor taskExecutor,
@Nonnull String exceptionMsg){
Thread mainThread = Thread.currentThread();
return CompletableFuture
.supplyAsync(normalFunction,taskExecutor)
.exceptionally(e -> {
log.error(exceptionMsg, e);
return exceptionFunction.get();
})
.whenComplete((data,e)->{
if(!mainThread.equals(Thread.currentThread())){
MallContextHolderManager.clearContext();
}
});
} }

使用Util创建任务代码

    private CompletableFuture<Boolean> asyncQueryCommentPic(ProductDetailInfoNewDto detailInfoDto, ProductInfoQueryDTO productInfoQuery) {
ThreadPoolTaskExecutor taskExecutor = bizThreadPoolManager.getBizThreadPoolTaskExecutor(BIZ_THREAD_POOL_NAME);
// 兜底获取不到线程池时降级
if (taskExecutor == null) {
detailInfoDto.setShowPrimaryPic(Boolean.FALSE);
return null;
}
return CompletableFutureUtil.executeWithFallbackAndContextPropagation(
() -> queryShowPrimaryPic(detailInfoDto, productInfoQuery),
() -> Boolean.FALSE,
taskExecutor,
"异步任务执行异常");
}

获取任务结果代码

    private void handShowPrimaryPic(ProductDetailInfoNewDto detailInfoDto, CompletableFuture<Boolean> commentPicFuture) {
detailInfoDto.setShowPrimaryPic(Boolean.FALSE);
if (commentPicFuture != null) {
try {
Boolean showPrimaryPic = commentPicFuture.get(asyncGetCommentPrimaryPicTimeout, TimeUnit.MILLISECONDS);
detailInfoDto.setShowPrimaryPic(showPrimaryPic);
} catch (Exception e) {
log.error("任务等待结果异常:future={}", JSON.toJSONString(commentPicFuture), e);
}
}
}

异步编程——CompletableFuture详解的更多相关文章

  1. php为什么需要异步编程?php异步编程的详解(附示例)

    本篇文章给大家带来的内容是关于php为什么需要异步编程?php异步编程的详解(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 我对 php 异步的知识还比较混乱,写这篇是为了 ...

  2. C# Socket-TCP异步编程原理详解附源码

    目录 目录异步原理主要方法源码Server源码:Client源码实验效果(广播为例)参考博客 TOC 异步原理 套接字编程原理:延续文件作用思想,打开-读写-关闭的模式. C/S编程模式如下: Ø 服 ...

  3. Javascript 异步加载详解

    Javascript 异步加载详解 本文总结一下浏览器在 javascript 的加载方式. 关键词:异步加载(async loading),延迟加载(lazy loading),延迟执行(lazy ...

  4. java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock

    原文:java并发编程 | 锁详解:AQS,Lock,ReentrantLock,ReentrantReadWriteLock 锁 锁是用来控制多个线程访问共享资源的方式,java中可以使用synch ...

  5. 异步编程CompletableFuture

    多线程优化性能,串行操作并行化 串行操作 // 以下2个都是耗时操作 doBizA(); doBizB(); 修改变为并行化 new Thread(() -> doBizA()).start() ...

  6. Java 异步编程 (5 种异步实现方式详解)

    ​ 同步操作如果遇到一个耗时的方法,需要阻塞等待,那么我们有没有办法解决呢?让它异步执行,下面我会详解异步及实现@mikechen 目录 什么是异步? 一.线程异步 二.Future异步 三.Comp ...

  7. python Gui编程工具详解:beeware

    各个gui开发工具对比 Flexx: 可以使用Flexx创建桌面应用程序和web应用程序,同时可以将程序导出到独立的HTML文档中,GitHub推荐 Kivy&BeeWare: 只需编写一套代 ...

  8. Java CompletableFuture 详解

    Future是Java 5添加的类,用来描述一个异步计算的结果.你可以使用isDone方法检查计算是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执 ...

  9. JavaScript面向对象编程—this详解

      this详解 作者的话 在JavaScriptOPPt面向对象编程中,this这位老大哥,相信大家不会陌生.大家在遇到this时,很多朋友难免会有个疑问:"这个this是什么,它到底指向 ...

  10. java网络编程(TCP详解)

    网络编程详解-TCP 一,TCP协议的特点              面向连接的协议(有发送端就一定要有接收端)    通过三次连接握手建立连接 通过四次握手断开连接 基于IO流传输数据 传输数据大小 ...

随机推荐

  1. [Java]多个参数的非空判断,不要再使用多个if挨个判断了!(多参数非空判断技巧)

    先上示例代码: if (StringUtils.isAnyBlank(form, to, subject, content)) { log.error("发送人,接收人,主题,内容均不可为空 ...

  2. Visual Studio - API调试与测试工具之HTTP文件

    后端开发,我们对于Api接口调试测试大致有以下方法:单元测试.Swagger.Postman. 但是每种方式也都有其局限性,几年前使用Visual Studio Code开发过一段时间,接触了REST ...

  3. 国外著名交易策略:R-Breaker模型设计原理(转载)

    R-Breaker是一种短线日内交易策略,它结合了趋势和反转两种交易方式. 交易系统的基本原理如下: 1.根据前一个交易日的收盘价.最高价和最低价数据通过一定方式计算出六个价位,从大到小依次为:突破买 ...

  4. Qt编写地图综合应用21-路径规划

    一.前言 近期重新将这个地图综合应用进行大幅度的改进更新升级,包括使用示例也做了非常多的改进和调整,其中就包括路径规划功能,之前只是调用了百度地图的JS交互接口,根据起始点坐标经纬度和结束点坐标经纬度 ...

  5. Many-shot Jailbreaking💘足够长的上下文长度有利于各种越狱?

    这篇文章虽然相较于上一篇图的对应有点迷,但是我感到了作者在强化学习与微调还有数学方面的深厚功底,我甚至感觉他的附录可以再发一篇文章了 这阶段的学习打开了我对越狱的思路~ 禁止盗用,侵权必究!!!欢迎大 ...

  6. [转]分享几款微软官方Office卸载工具与使用方法

    更换Office版本时,需要先卸载旧版Office,如果是卸载普通软件,我们只需在"控制面板"里点击"卸载"就能轻松实现,但这种方法对卸载Office 可能无效 ...

  7. IM通讯协议专题学习(七):手把手教你如何在NodeJS中从零使用Protobuf

    1.前言 Protobuf是Google开源的一种混合语言数据标准,已被各种互联网项目大量使用. Protobuf最大的特点是数据格式拥有极高的压缩比,这在移动互联时代是极具价值的(因为移动网络流量到 ...

  8. IM跨平台技术学习(四):蘑菇街基于Electron开发IM客户端的技术实践

    本文由蘑菇街前端技术团队分享,原题"Electron 从零到一",有修订和改动. 1.引言 本系列文章的前面几篇主要是从Electron技术本身进行了讨论(包括:第1篇初步了解El ...

  9. Solution -「NOI 2017」「洛谷 P3826」蔬菜

    \(\mathscr{Description}\)   Link.   原题意比较简洁了.注意一下卖出的菜也会变质,且让它们代替未卖出的菜变质是更优的. \(\mathscr{Solution}\) ...

  10. JS利用浏览器进行语言识别

    JS利用浏览器进行语言识别 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...