热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

编程老司机带你玩转CompletableFuture异步编程

本文从实例出发,介绍CompletableFuture基本用法。不过讲的再多,不如亲自上手练习一下。所以建议各位小伙伴看完,上机练习一把,快速掌握CompletableFuture

本文从实例出发,介绍 CompletableFuture 基本用法。不过讲的再多,不如亲自上手练习一下。所以建议各位小伙伴看完,上机练习一把,快速掌握 CompletableFuture

个人博文地址:https://sourl.cn/s5MbCm

全文摘要:

  • Future VS CompletableFuture
  • CompletableFuture 基本用法

0x00. 前言

一些业务场景我们需要使用多线程异步执行任务,加快任务执行速度。 Java 提供 Runnable Future 两个接口用来实现异步任务逻辑。

虽然 Future 可以获取任务执行结果,但是获取方式十方不变。我们不得不使用Future#get 阻塞调用线程,或者使用轮询方式判断 Future#isDone 任务是否结束,再获取结果。

这两种处理方式都不是很优雅,JDK8 之前并发类库没有提供相关的异步回调实现方式。没办法,我们只好借助第三方类库,如 Guava,扩展 Future,增加支持回调功能。相关代码如下:

编程老司机带你玩转 CompletableFuture 异步编程

虽然这种方式增强了 Java 异步编程能力,但是还是无法解决多个异步任务需要相互依赖的场景。

举一个生活上的例子,假如我们需要出去旅游,需要完成三个任务:

  • 任务一:订购航班
  • 任务二:订购酒店
  • 任务三:订购租车服务

很显然任务一和任务二没有相关性,可以单独执行。但是任务三必须等待任务一与任务二结束之后,才能订购租车服务。

为了使任务三时执行时能获取到任务一与任务二执行结果,我们还需要借助 CountDownLatch

编程老司机带你玩转 CompletableFuture 异步编程

0x01. CompletableFuture

JDK8 之后,Java 新增一个功能十分强大的类:CompletableFuture。单独使用这个类就可以轻松的完成上面的需求:

编程老司机带你玩转 CompletableFuture 异步编程

大家可以先不用管 CompletableFuture 相关 API,下面将会具体讲解。

对比 FutureCompletableFuture 优点在于:

  • 不需要手工分配线程,JDK 自动分配
  • 代码语义清晰,异步任务链式调用
  • 支持编排异步任务

怎么样,是不是功能很强大?接下来抓稳了,小黑哥要发车了。

编程老司机带你玩转 CompletableFuture 异步编程

1.1 方法一览

首先来通过 IDE 查看下这个类提供的方法:

编程老司机带你玩转 CompletableFuture 异步编程

稍微数一下,这个类总共有 50 多个方法,我的天。。。

编程老司机带你玩转 CompletableFuture 异步编程

不过也不要怕,小黑哥帮你们归纳好了,跟着小黑哥的节奏,带你们掌握 CompletableFuture

若图片不清晰,可以关注『程序通事』,回复:『233』,获取该思维导图

编程老司机带你玩转 CompletableFuture 异步编程

1.2 创建 CompletableFuture 实例

创建 CompletableFuture 对象实例我们可以使用如下几个方法:

编程老司机带你玩转 CompletableFuture 异步编程

第一个方法创建一个具有默认结果的 CompletableFuture,这个没啥好讲。我们重点讲述下下面四个异步方法。

前两个方法 runAsync 不支持返回值,而 supplyAsync可以支持返回结果。

这个两个方法默认将会使用公共的 ForkJoinPool 线程池执行,这个线程池默认线程数是 CPU 的核数。

可以设置 JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism 来设置 ForkJoinPool 线程池的线程数

使用共享线程池将会有个弊端,一旦有任务被阻塞,将会造成其他任务没机会执行。所以强烈建议使用后两个方法,根据任务类型不同,主动创建线程池,进行资源隔离,避免互相干扰。

1.3 设置任务结果

CompletableFuture 提供以下方法,可以主动设置任务结果。

 boolean complete(T value)
 boolean completeExceptionally(Throwable ex)

第一个方法,主动设置 CompletableFuture 任务执行结果,若返回 true,表示设置成功。如果返回 false,设置失败,这是因为任务已经执行结束,已经有了执行结果。

示例代码如下:

// 执行异步任务
CompletableFuture cf = CompletableFuture.supplyAsync(() -> {
  System.out.println("cf 任务执行开始");
  sleep(10, TimeUnit.SECONDS);
  System.out.println("cf 任务执行结束");
  return "楼下小黑哥";
});
//
Executors.newSingleThreadScheduledExecutor().execute(() -> {
  sleep(5, TimeUnit.SECONDS);
  System.out.println("主动设置 cf 任务结果");
  // 设置任务结果,由于 cf 任务未执行结束,结果返回 true
  cf.complete("程序通事");
});
// 由于 cf 未执行结束,将会被阻塞。5 秒后,另外一个线程主动设置任务结果
System.out.println("get:" + cf.get());
// 等待 cf 任务执行结束
sleep(10, TimeUnit.SECONDS);
// 由于已经设置任务结果,cf 执行结束任务结果将会被抛弃
System.out.println("get:" + cf.get());
/***
   * cf 任务执行开始
   * 主动设置 cf 任务结果
   * get:程序通事
   * cf 任务执行结束
   * get:程序通事
*/

这里需要注意一点,一旦 complete 设置成功,CompletableFuture 返回结果就不会被更改,即使后续 CompletableFuture 任务执行结束。

第二个方法,给 CompletableFuture 设置异常对象。若设置成功,如果调用 get 等方法获取结果,将会抛错。

示例代码如下:

// 执行异步任务
CompletableFuture cf = CompletableFuture.supplyAsync(() -> {
    System.out.println("cf 任务执行开始");
    sleep(10, TimeUnit.SECONDS);
    System.out.println("cf 任务执行结束");
    return "楼下小黑哥";
});
//
Executors.newSingleThreadScheduledExecutor().execute(() -> {
    sleep(5, TimeUnit.SECONDS);
    System.out.println("主动设置 cf 异常");
    // 设置任务结果,由于 cf 任务未执行结束,结果返回 true
    cf.completeExceptionally(new RuntimeException("啊,挂了"));
});
// 由于 cf 未执行结束,前 5 秒将会被阻塞。后续程序抛出异常,结束
System.out.println("get:" + cf.get());
/***
 * cf 任务执行开始
 * 主动设置 cf 异常
 * java.util.concurrent.ExecutionException: java.lang.RuntimeException: 啊,挂了
 * ......
 */

1.4 CompletionStage

CompletableFuture 分别实现两个接口 FutureCompletionStage

编程老司机带你玩转 CompletableFuture 异步编程

Future 接口大家都比较熟悉,这里主要讲讲 CompletionStage

CompletableFuture 大部分方法来自CompletionStage 接口,正是因为这个接口,CompletableFuture才有如从强大功能。

想要理解 CompletionStage 接口,我们需要先了解任务的时序关系的。我们可以将任务时序关系分为以下几种:

  • 串行执行关系
  • 并行执行关系
  • AND 汇聚关系
  • OR 汇聚关系

1.5 串行执行关系

任务串行执行,下一个任务必须等待上一个任务完成才可以继续执行。

编程老司机带你玩转 CompletableFuture 异步编程

CompletionStage 有四组接口可以描述串行这种关系,分别为:

编程老司机带你玩转 CompletableFuture 异步编程

thenApply 方法需要传入核心参数为 Function类型。这个类核心方法为:

 R apply(T t)

所以这个接口将会把上一个任务返回结果当做入参,执行结束将会返回结果。

thenAccept 方法需要传入参数对象为 Consumer类型,这个类核心方法为:

void accept(T t)

返回值 void 可以看出,这个方法不支持返回结果,但是需要将上一个任务执行结果当做参数传入。

thenRun 方法需要传入参数对象为 Runnable 类型,这个类大家应该都比较熟悉,核心方法既不支持传入参数,也不会返回执行结果。

thenCompose 方法作用与 thenApply 一样,只不过 thenCompose 需要返回新的 CompletionStage。这么理解比较抽象,可以集合代码一起理解。

编程老司机带你玩转 CompletableFuture 异步编程

方法中带有 Async ,代表可以异步执行,这个系列还有重载方法,可以传入自定义的线程池,上图未展示,读者只可以自行查看 API。

最后我们通过代码展示 thenApply 使用方式:

CompletableFuture cf
        = CompletableFuture.supplyAsync(() -> "hello,楼下小黑哥")// 1
        .thenApply(s -> s + "@程序通事") // 2
        .thenApply(String::toUpperCase); // 3
System.out.println(cf.join());
// 输出结果 HELLO,楼下小黑哥@程序通事

这段代码比较简单,首先我们开启一个异步任务,接着串行执行后续两个任务。任务 2 需要等待任务1 执行完成,任务 3 需要等待任务 2。

上面方法,大家需要记住了 FunctionConsumerRunnable 三者区别,根据场景选择使用。

1.6 AND 汇聚关系

AND 汇聚关系代表所有任务完成之后,才能进行下一个任务。

编程老司机带你玩转 CompletableFuture 异步编程

如上所示,只有任务 A 与任务 B 都完成之后,任务 C 才会开始执行。

CompletionStage 有以下接口描述这种关系。

编程老司机带你玩转 CompletableFuture 异步编程

thenCombine 方法核心参数 BiFunction ,作用与 Function一样,只不过 BiFunction 可以接受两个参数,而 Function 只能接受一个参数。

thenAcceptBoth 方法核心参数BiConsumer 作用也与 Consumer一样,不过其需要接受两个参数。

runAfterBoth 方法核心参数最简单,上面已经介绍过,不再介绍。

这三组方法只能完成两个任务 AND 汇聚关系,如果需要完成多个任务汇聚关系,需要使用 CompletableFuture#allOf,不过这里需要注意,这个方法是不支持返回任务结果。

AND 汇聚关系相关示例代码,开头已经使用过了,这里再粘贴一下,方便大家理解:

编程老司机带你玩转 CompletableFuture 异步编程

1.7 OR 汇聚关系

有 AND 汇聚关系,当然也存在 OR 汇聚关系。OR 汇聚关系代表只要多个任务中任一任务完成,就可以接着接着执行下一任务。

编程老司机带你玩转 CompletableFuture 异步编程

CompletionStage 有以下接口描述这种关系:

编程老司机带你玩转 CompletableFuture 异步编程

前面三组接口方法传参与 AND 汇聚关系一致,这里也不再详细解释了。

当然 OR 汇聚关系可以使用 CompletableFuture#anyOf 执行多个任务。

下面示例代码展示如何使用 applyToEither 完成 OR 关系。

CompletableFuture cf
        = CompletableFuture.supplyAsync(() -> {
    sleep(5, TimeUnit.SECONDS);
    return "hello,楼下小黑哥";
});// 1

CompletableFuture cf2 = cf.supplyAsync(() -> {
    sleep(3, TimeUnit.SECONDS);
    return "hello,程序通事";
});
// 执行 OR 关系
CompletableFuture cf3 = cf2.applyToEither(cf, s -> s);

// 输出结果,由于 cf2 只休眠 3 秒,优先执行完毕
System.out.println(cf2.join());
// 结果:hello,程序通事

1.8 异常处理

CompletableFuture 方法执行过程若产生异常,当调用 getjoin 获取任务结果才会抛出异常。

编程老司机带你玩转 CompletableFuture 异步编程

上面代码我们显示使用 try..catch 处理上面的异常。不过这种方式不太优雅,CompletionStage 提供几个方法,可以优雅处理异常。

编程老司机带你玩转 CompletableFuture 异步编程

exceptionally 使用方式类似于 try..catchcatch代码块中异常处理。

whenCompletehandle 方法就类似于 try..catch..finanllyfinally 代码块。无论是否发生异常,都将会执行的。这两个方法区别在于 handle 支持返回结果。

下面示例代码展示 handle 用法:

CompletableFuture
        f0 = CompletableFuture.supplyAsync(() -> (7 / 0))
        .thenApply(r -> r * 10)
        .handle((integer, throwable) -> {
            // 如果异常存在,打印异常,并且返回默认值
            if (throwable != null) {
                throwable.printStackTrace();
                return 0;
            } else {
                // 如果
                return integer;
            }
        });


System.out.println(f0.join());
/**
 *java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
 * .....
 * 
 * 0
 */

0x02. 总结

JDK8 提供 CompletableFuture 功能非常强大,可以编排异步任务,完成串行执行,并行执行,AND 汇聚关系,OR 汇聚关系。

不过这个类方法实在太多,且方法还需要传入各种函数式接口,新手刚开始使用会直接会被弄懵逼。这里帮大家在总结一下三类核心参数的作用

  • Function 这类函数接口既支持接收参数,也支持返回值
  • Consumer 这类接口函数只支持接受参数,不支持返回值
  • Runnable 这类接口不支持接受参数,也不支持返回值

搞清楚函数参数作用以后,然后根据串行,AND 汇聚关系,OR 汇聚关系归纳一下相关方法,这样就比较好理解了

最后再贴一下,文章开头的思维导图,希望对你有帮助。

编程老司机带你玩转 CompletableFuture 异步编程

0x03. 帮助文档

  1. 极客时间-并发编程专栏
  2. https://colobu.com/2016/02/29/Java-CompletableFuture
  3. https://www.ibm.com/developerworks/cn/java/j-cf-of-jdk8/index.html

最后说一句(求关注)

CompletableFuture 很早之前就有关注,本以为跟 Future一样,使用挺简单,谁知道学的时候才发现好难。各种 API 方法看的头有点大。

后来看到极客时间-『并发编程』专栏使用归纳方式分类 CompletableFuture 各种方法,一下子就看懂了。所这篇文章也参考这种归纳方式。

这篇文章找资料,整理一个星期,幸好今天顺利产出。

看在小黑哥写的这么辛苦的份上,点个关注吧,赏个赞呗。别下次一定啊,大哥!写文章很辛苦的,需要来点正反馈。

才疏学浅,难免会有纰漏,如果你发现了错误的地方,还请你留言给我指出来,我对其加以修改。

感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注~

欢迎关注我的公众号:程序通事,获得日常干货推送。如果您对我的专题内容感兴趣,也可以关注我的博客:studyidea.cn


推荐阅读
  • 多线程基础概览
    本文探讨了多线程的起源及其在现代编程中的重要性。线程的引入是为了增强进程的稳定性,确保一个进程的崩溃不会影响其他进程。而进程的存在则是为了保障操作系统的稳定运行,防止单一应用程序的错误导致整个系统的崩溃。线程作为进程的逻辑单元,多个线程共享同一CPU,需要合理调度以避免资源竞争。 ... [详细]
  • Python多线程编程技巧与实战应用详解 ... [详细]
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
  • 深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案
    深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案 ... [详细]
  • 本文深入探讨了Java多线程环境下的同步机制及其应用,重点介绍了`synchronized`关键字的使用方法和原理。`synchronized`关键字主要用于确保多个线程在访问共享资源时的互斥性和原子性。通过具体示例,如在一个类中使用`synchronized`修饰方法,展示了如何实现线程安全的代码块。此外,文章还讨论了`ReentrantLock`等其他同步工具的优缺点,并提供了实际应用场景中的最佳实践。 ... [详细]
  • python模块之正则
    re模块可以读懂你写的正则表达式根据你写的表达式去执行任务用re去操作正则正则表达式使用一些规则来检测一些字符串是否符合个人要求,从一段字符串中找到符合要求的内容。在 ... [详细]
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • MySQL 5.7 学习指南:SQLyog 中的主键、列属性和数据类型
    本文介绍了 MySQL 5.7 中主键(Primary Key)和自增(Auto-Increment)的概念,以及如何在 SQLyog 中设置这些属性。同时,还探讨了数据类型的分类和选择,以及列属性的设置方法。 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 深入解析 Lifecycle 的实现原理
    本文将详细介绍 Android Jetpack 中 Lifecycle 组件的实现原理,帮助开发者更好地理解和使用 Lifecycle,避免常见的内存泄漏问题。 ... [详细]
  • 在使用 Cacti 进行监控时,发现已运行的转码机未产生流量,导致 Cacti 监控界面显示该转码机处于宕机状态。进一步检查 Cacti 日志,发现数据库中存在 SQL 查询失败的问题,错误代码为 145。此问题可能是由于数据库表损坏或索引失效所致,建议对相关表进行修复操作以恢复监控功能。 ... [详细]
  • Java并发机制详解及其在数据安全性保障中的应用方案 ... [详细]
  • 在PHP多线程扩展开发中,面临的主要挑战之一是多线程调用PHP用户类方法时可能出现的内存错误。具体表现为当多个线程同时调用同一个类实例的同一方法时,系统会抛出内存错误。为了解决这一问题,本文深入分析了PHP多线程扩展的实现机制,并提出了几种有效的解决方案和技术思路,包括线程安全的类设计、内存管理优化以及线程同步机制的改进。通过这些方法,可以显著提升PHP多线程扩展的稳定性和性能。 ... [详细]
  • 在 Linux 环境下,多线程编程是实现高效并发处理的重要技术。本文通过具体的实战案例,详细分析了多线程编程的关键技术和常见问题。文章首先介绍了多线程的基本概念和创建方法,然后通过实例代码展示了如何使用 pthreads 库进行线程同步和通信。此外,还探讨了多线程程序中的性能优化技巧和调试方法,为开发者提供了宝贵的实践经验。 ... [详细]
author-avatar
木子0031970_651
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有