Java 并发编程实战
王宝令
资深架构师
72485 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 51 讲
学习攻略 (1讲)
Java 并发编程实战
15
15
1.0x
00:00/00:00
登录|注册

24 | CompletableFuture:异步编程没那么难

handle
whenComplete
exceptionally
runAfterEither 系列
acceptEither 系列
applyToEither 系列
runAfterBoth 系列
thenAcceptBoth 系列
thenCombine 系列
thenCompose 系列
thenRun 系列
thenAccept 系列
thenApply 系列
异常处理
描述 OR 汇聚关系
描述 AND 汇聚关系
描述串行关系
可以指定线程池
使用默认线程池
代码更简练
语义更清晰
无需手工维护线程
课后思考
总结
如何理解 CompletionStage 接口
创建 CompletableFuture 对象
CompletableFuture 的核心优势
为什么异步编程大火了
CompletableFuture 异步编程

该思维导图由 AI 生成,仅供参考

前面我们不止一次提到,用多线程优化性能,其实不过就是将串行操作变成并行操作。如果仔细观察,你还会发现在串行转换成并行的过程中,一定会涉及到异步化,例如下面的示例代码,现在是串行的,为了提升性能,我们得把它们并行化,那具体实施起来该怎么做呢?
//以下两个方法都是耗时操作
doBizA();
doBizB();
还是挺简单的,就像下面代码中这样,创建两个子线程去执行就可以了。你会发现下面的并行方案,主线程无需等待 doBizA() 和 doBizB() 的执行结果,也就是说 doBizA() 和 doBizB() 两个操作已经被异步化了。
new Thread(()->doBizA())
.start();
new Thread(()->doBizB())
.start();
异步化,是并行方案得以实施的基础,更深入地讲其实就是:利用多线程优化性能这个核心方案得以实施的基础看到这里,相信你应该就能理解异步编程最近几年为什么会大火了,因为优化性能是互联网大厂的一个核心需求啊。Java 在 1.8 版本提供了 CompletableFuture 来支持异步编程,CompletableFuture 有可能是你见过的最复杂的工具类了,不过功能也着实让人感到震撼。

CompletableFuture 的核心优势

为了领略 CompletableFuture 异步编程的优势,这里我们用 CompletableFuture 重新实现前面曾提及的烧水泡茶程序。首先还是需要先完成分工方案,在下面的程序中,我们分了 3 个任务:任务 1 负责洗水壶、烧开水,任务 2 负责洗茶壶、洗茶杯和拿茶叶,任务 3 负责泡茶。其中任务 3 要等待任务 1 和任务 2 都完成后才能开始。这个分工如下图所示。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

CompletableFuture是Java 1.8版本引入的工具类,用于支持异步编程,其核心优势在于简化了多线程编程的复杂性。通过CompletableFuture,可以轻松实现任务的并行化和串行化,并清晰地描述任务之间的时序关系。文章首先介绍了异步编程的基本概念,然后通过烧水泡茶的例子展示了CompletableFuture的使用优势。接着详细介绍了如何创建CompletableFuture对象以及如何理解CompletionStage接口。CompletionStage接口可以描述串行关系、AND聚合关系、OR聚合关系以及异常处理,通过thenApply、thenAccept、thenRun和thenCompose等系列方法实现。文章通过示例代码清晰地展示了这些方法的使用。总的来说,CompletableFuture为异步编程提供了强大的支持,简化了多线程编程的复杂性,使得任务的并行化和串行化变得更加清晰和简洁。文章还提到了异常处理和异步编程的发展趋势,以及对RxJava项目的推荐。文章内容丰富,对异步编程有很好的介绍和指导作用。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 并发编程实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(79)

  • 最新
  • 精选
  • J.M.Liu
    思考题: 1.没有进行异常处理, 2.要指定专门的线程池做数据库查询 3.如果检查和查询都比较耗时,那么应该像之前的对账系统一样,采用生产者和消费者模式,让上一次的检查和下一次的查询并行起来。 另外,老师把javadoc里那一堆那一堆方法进行了分类,分成串行、并行、AND聚合、OR聚合,简直太棒了,一下子就把这些方法纳入到一个完整的结构体系里了。简直棒

    作者回复: 思考题考虑的很全面👍

    2019-04-23
    4
    144
  • 袁阳
    思考题: 1,读数据库属于io操作,应该放在单独线程池,避免线程饥饿 2,异常未处理

    作者回复: 👍👍

    2019-04-23
    7
    118
  • 密码123456
    我在想一个问题,明明是串行过程,直接写就可以了。为什么还要用异步去实现串行?

    作者回复: 这个简单场景没必要用

    2019-04-23
    5
    49
  • 发条橙子 。
    老师 ,我有个疑问。 completableFuture 中各种关系(并行、串行、聚合),实际上就覆盖了各种需求场景。 例如 : 线程A 等待 线程B 或者 线程C 等待 线程A和B 。 我们之前讲的并发包里面 countdownLatch , 或者 threadPoolExecutor 和future 就是来解决这些关系场景的 , 那有了 completableFuture 这个类 ,是不是以后有需求都优先考虑用 completableFuture ?感觉这个类就可以解决前面所讲的类的问题了

    作者回复: 我觉得可以优先使用CompletableFuture,当然前提是你的jdk是1.8

    2019-04-24
    2
    45
  • 青莲
    1.查数据库属于io操作,用定制线程池 2.查出来的结果做为下一步处理的条件,若结果为空呢,没有对应处理 3.缺少异常处理机制

    作者回复: 👍👍

    2019-04-23
    22
  • 笃行之
    ”如果所有 CompletableFuture 共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能。”老师,阻塞在io上和是不是在一个线程池没关系吧?

    作者回复: 有关系,如果系统就一个线程池,里面的线程都阻塞在io上,那么系统其他的任务都需要等待。如果其他任务有自己的线程池,就没有问题。

    2019-04-29
    2
    17
  • J.M.Liu
    我觉得既然都讲到CompletableFuture了,老师是不是有必要不一章ForkJoinPool呀?毕竟,ForkJoinPool和ThreadPoolExecutor还是有很多不一样的。谢谢老师

    作者回复: 后面有介绍

    2019-04-23
    12
  • henry
    老师我现在有个任务,和您的例子有相似的地方,是从一个库里查询多张表的数据同步到另外一个库,就有双重for循环,最外层用与多张表的遍历,内层的for循环用于批量读取某一张表的数据,因为数据量可能在几万条,我想分批次读出来再同步到另一个数据库,昨天写的时候用的是futuretask,今天正好看到老师的文章就改成了CompletableFuture,还没有用异常处理的,后面我还要看看怎么加上异常处理的。其它的不知道我用的对不对,请老师看看: // 初始化异步工具类,分别异步执行2个任务 CompletableFuture<List<PBSEnergyData>> asyncAquirePBSEnergyData = new CompletableFuture(); CompletableFuture<List<AXEEnergyData>> asyncSaveAxeEnergyData = new CompletableFuture(); // 初始化两个线程池, 分别用于2个任务 ,1个任务一个线程池,互不干扰 Executor aquirePBSEnergyDataExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); Executor saveAxeEnergyDataExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); queryUtils.getTableNames().forEach(tableName -> { int pageSize = queryUtils.getPageSize(); //查询该表有多少条数据,每${pageSize}条一次 int count = pbsEnergyService.getCount(tableName); //总页数 int pages = count / pageSize; int pageNum = 0; final int pageNo = pageNum; for(pageNum = 0; pageNum <= pages; pageNum++){ // 异步获取PBS数据库的数据并返回结果 asyncAquirePBSEnergyData .supplyAsync(() -> { 查询数据库 return pbsEnergyDatas; },aquirePBSEnergyDataExecutor) // 任务2任务1,任务1返回的结果 .thenApply(pbsEnergyDatas -> asyncSaveAxeEnergyData.runAsync(()->{ List<AXEEnergyData> axeEnergyDatas = pbsEnergyDatas.stream().map(pbsEnergyData -> { //进行类型转换 }).collect(Collectors.toList()); //批量保存 },saveAxeEnergyDataExecutor)); } }); 全部贴上去,超过字符数了,只能请老师凑合看了 :(

    作者回复: 有个地方需要注意:runAsync和supplyAsync都是静态方法。 线程池设置的太小了,这是个IO密集型的任务 thenApply里面的runAsync我觉得好像是没有必要,增加了复杂的了。 如果thenApply里面需要异步,可以用thenApplyAsync

    2019-04-24
    4
    11
  • Chocolate
    回答「密码123456」:CompletableFuture 在执行的过程中可以不阻塞主线程,支持 runAsync、anyOf、allOf 等操作,等某个时间点需要异步执行的结果时再阻塞获取。

    作者回复: 是的,复杂场景就能体现出优势了

    2019-04-23
    4
    9
  • Monday
    CompletableFuture从来没玩过,老师在工作/实践中有使用过这个类吗?

    作者回复: 用过,配合lambda效果很好

    2019-12-23
    7
收起评论
显示
设置
留言
79
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部