Java 为并发编程提供了众多的工具,本文将重点介绍 Java8 中 CompletableFuture。

笔者在自己搜索资料及实践之后,避开已经存在的优秀文章的写作内容与思路,将以更加浅显的示例和语言,介绍 CompleatableFuture, 同时提供自己的思考。

本文最后会附上其他优秀的文章链接供读者进行更详细学习与理解。

1. 理解 Future

当处理一个任务时,总会遇到以下几个阶段:提交任务

执行任务

任务完成的后置处理

以下我们简单定义,构造及提交任务的线程为生产者线程; 执行任务的线程为消费者线程; 任务的后置处理线程为后置消费者线程。

根据任务的特性,会衍生各种各样的线程模型。其中包括 Future 模式。

接下来我们先用最简单的例子迅速对 Future 有个直观理解,然后再对其展开讨论。

例1.1

ExecutorService executor = Executors.newFixedThreadPool(3);

Future future = executor.submit(new Callable() {

@Override

public String call() throws Exception {

//do some thing Thread.sleep(100);

return "i am ok";

}

});

println(future.isDone());

println(future.get());

在本例中首先创建一个线程池,然后向线程池中提交了一个任务, submit 提交任务后会被立即返回,而不会等到任务实际处理完成才会返回, 而任务提交后返回值便是 Future, 通过 Future 我们可以调用 get() 方法阻塞式的获取返回结果, 也可以使用 isDone 获取任务是否完成

生产者线程在提交完任务后,有两个选择:关注处理结果和不关注处理结果。处理结果包括任务的返回值,也包含任务是否正确完成,中途是否抛出异常等等。

Future 模式提供一种机制,在消费者异步处理生产者提交的任务的情况下,生产者线程也可以拿到消费者线程的处理结果,同时通过 Future 也可以取消掉处理中的任务。

在实际的开发中,我们经常会遇到这种类似需求。任务需要异步处理,同时又关心任务的处理结果,此时使用 Future 是再合适不过了。

1.2 Future 如何被构建的

Future 是如何被创建的呢? 生产者线程提交给消费者线程池任务时,线程池会构造一个实现了 Future 接口的对象 FutureTask 。该对象相当于是消费者和生产者的桥梁,消费者通过 FutureTask 存储任务的处理结果,更新任务的状态:未开始、正在处理、已完成等。而生产者拿到的 FutureTask 被转型为 Future 接口,可以阻塞式获取任务的处理结果,非阻塞式获取任务处理状态。

更细节的实现机制,读者可以参考 JDK 中的 FutureTask 类。

1.3 Java 之外的一些思考

我一直将 Future 视为消费者线程和生产者线程关于该任务的一个通道, Java 中通过共享对象来进行跨线程通信,并且提供了各种工具来保证共享对象的线程安全性,Future 是一个典型通过共享内存来通信的例子,而熟悉 Go 语言的读者会想到,协程间通信的方式是通过 channel。

就像 Go 语言信仰的那句话:不要通过共享内存来通信,而应该通过通信来共享内存 Go 所作的就是通过 channel,通过通信共享了内存,共享了数据。

通过对 Future 的示例,我们了解了 Future 在任务生产者和消费者之间的起到的桥梁作用。

但是我们解决的问题是文中开头提到的任务处理三大阶段中的最后一个阶段–任务完成的后置处理。而本文介绍的重点 CompletableFuture 提供的编程模型,可以让我们随心所欲地优雅地处理后置结果。

2. 任务结果的花式处理

有两种可能生产者不需要关注的任务的处理结果:第一种可能,处理结果并不影响后续的业务逻辑。

另一种可能,被提交的任务在结束前,会将自身的处理结果上报到其他结构中,例如 MQ,DB,Redis 等等, 这些任务的处理结果会被其他的协调者或者调度者跟踪和监控,不再需要生产者关心。

这样生产者只负责生产并且提交任务,而完全不用关心任务的处理和任务结果的处理。在这种编程模型中,实现了充分的解耦。但是也增加了系统的复杂度, 任务状态和结果需要额外的监控及管控, 这种处理方式场景复杂,吞吐量大的分布式系统中有着广泛的应用。

但另外一种更简单的系统设计,要求生产者需要关心处理结果,根据处理结果执行后续的任务处理, CompletableFuture 正是为此而生。

2.1 Future 的改造

在第一节中我们简单介绍了 Future, 但是我们给的例子获取结果都是使用 get 阻塞式,实际开发中,我们并不希望生产者线程会被阻塞住, 但是又希望当我们提交完任务后可以通过 Future 处理结果,如何实现呢?

在 1.2 中我们讨论 Future 是如何被构造的时候,曾说过 消费者线程在执行 Task 后,会将处理结果 set 到 Future 中,那么我们为什么不利用 set 方法,为我们提供一种后置处理的机制呢?

思路是在调用 set 方法后执行一系列后置处理,这些后置处理是生产者在提交 Task 时指定的。这样虽然执行后置处理的线程并不是生产者线程,但实际上处理逻辑是有生产者指定的。

CompletableFuture 基于这种机制为我们提供了很多后置处理的执行方式,同时又提供了很多整合多个 Future 的方法。我们可以使用 CompletableFuture 灵活的处理多个 Task 协同处理的问题。

2.2 复杂任务的示例说明

在某些业务场景中,任务处理并不是简单地执行一条 SQL, 某些长任务需要被拆解为很多小任务,而这些小任务有些可以并行处理,有些是有依赖顺序的。假设有如下一个长任务:

Task A 如下子任务:可并行处理的 Task1.1、Task1.2、Task1.3。

依据第一步 Task1.1、 1.2.1.3 三个任务的结果,执行 Task2 。

根据 Task2 的结果,异步的执行 Task 3.1。

TaskA 是一个比较复杂的任务,需要拆分多个子任务,其中子任务中也会涉及到子任务。如何保证这些任务的依赖关系,同时保证任务可以得到异步处理呢?

例2.2

future1.thenCombine(future2, (args1, args2) -> { ### Task 1

println(args1);

println(args2);

return "3";

}).thenApply((res) -> { ### Task 2

println(res);

return "4";

}).thenApplyAsync((res) -> { ### Task 3

println(res);

return "5";

});

在例 2.2 中 future1.future2 都完成时,执行了 Combine 动作,Combine 会生成新的 Future。新的 Future 完成后将执行 thenApply, 对合并产生的结果再次处理,最后再次对结果处理,而此次处理是异步执行,即后置处理的线程和任务的消费者线程不是同一个线程。

例 2.2 只是一个 CompletableFuture 的简单使用, CompletableFuture 为我们提供了非常多的方法, 笔者将其所有方法按照功能分类如下:对一个或多个 Future 合并操作,生成一个新的 Future, 参考 allOf,anyOf,runAsync, supplyAsync。

为 Future 添加后置处理动作, thenAccept, thenApply, thenRun。

两个人 Future 任一或全部完成时,执行后置动作:applyToEither, acceptEither, thenAcceptBothAsync, runAfterBoth,runAfterEither 等。

当 Future 完成条件满足时,异步或同步执行后置处理动作: thenApplyAsync, thenRunAsync。所有异步后置处理都会添加 Async 后缀。

定义 Future 的处理顺序 thenCompose 协同存在依赖关系的 Future,thenCombine。合并多个 Future的处理结果返回新的处理结果。

异常处理 exceptionally ,如果任务处理过程中抛出了异常。

我们需要明白 CompletableFuture 提供了一些方法组合新的 Future,组合条件依赖顺序执行或并行执行。

提供另一些方法指定 Future 的完成条件,以及要执行的后置处理,后置处理包括 apply, accept, run apply 类型的后置处理带返回值,也就是要生成新的处理结果。

Accept 代表对处理结果进行消费,但是并不产生新的处理结果。而 run 更加简单,既没有上一次处理结果的输入,也没有返回处理结果。

通过三种类型的后置处理,可以组合一个链式处理的后置处理。 后置处理可以不由消费者线程执行,可在线程池中单独执行。这样从生产者,消费者,后置处理三个阶段都可以异步执行。

CompletableFuture 实现了 Future 以及 ComplatableStage 接口, 实现 Future 接口代表其本身可以作为生产者和消费者的 “桥梁”, 而 ComplatableStage 接口定义了以上所有的组合条件。

完成条件,后置处理的多种类型众多的 API, 可以说 Future 接口只是描述了单个任务处理的方式, 而 CompletableStage 接口更进一步的从实际的编程需求出发,满足了多个任务协同处理的场景需求,包括多个任务任一完成或全部完成时。

任务串行顺序执行, 并行执行,并且创造性的提出了 apply, accept, run 三种后置处理器的类型,本质上后置处理还是链式顺序执行的。这样在众多子任务的场景需求中, CompletableFuture 可以很好的胜任。

由于 CompletableFuture 的 API 众多,笔者只是按照自己的理解按照功能不同做了分类。读者如果更深入的理解还是需要实际动手,文末提供了非常不错的相关 API 使用教程

本文作者:于海强滴滴云-为开发者而生​www.didiyun.com

java8 协程_Java8 异步编程—CompletableFuture相关推荐

  1. python3异步编程_协程 Python异步编程(asyncio)

    协程(Coroutine) 也可以被称为微线程,是一种用户态内的上下文切换技术.简而言之,其实就是通过一个线程实现代码块相互切换执行. 直接上代码,例如: 同步编程 import time def f ...

  2. python协程和异步编程

    文章目录 协程 & 异步编程(asyncio) 1. 协程的实现 1.1 greenlet 1.2 yield 1.3 asyncio 1.4 async & awit 1.5 小结 ...

  3. python——asyncio模块实现协程、异步编程(一)

    我们都知道,现在的服务器开发对于IO调度的优先级控制权已经不再依靠系统,都希望采用协程的方式实现高效的并发任务,如js.lua等在异步协程方面都做的很强大. python在3.4版本也加入了协程的概念 ...

  4. python——asyncio模块实现协程、异步编程(三)

    [八]协程停止 future对象有几个状态: Pending Running Done Cancelled 创建future的时候,task为pending,事件循环调用执行的时候当然就是runnin ...

  5. python——asyncio模块实现协程、异步编程(二)

    [六]协程并发 定义tasks时可以设置多个ensure,也可以像多线程那样用append方法实现 tasks = [asyncio.ensure_future(coroutine1),asyncio ...

  6. python3异步协程爬虫_Python实现基于协程的异步爬虫

    Python实现基于协程的异步爬虫 一.课程介绍 1. 课程来源 本课程核心部分来自<500 lines or less>项目,作者是来自 MongoDB 的工程师 A. Jesse Ji ...

  7. python异步爬虫_Python实现基于协程的异步爬虫

    Python实现基于协程的异步爬虫 一.课程介绍 1. 课程来源 本课程核心部分来自<500 lines or less>项目,作者是来自 MongoDB 的工程师 A. Jesse Ji ...

  8. 《Java8实战》读书笔记10:组合式异步编程 CompletableFuture

    <Java8实战>读书笔记10:组合式异步编程 CompletableFuture 第11章 CompletableFuture:组合式异步编程 11.1 Future 接口 (只是个引子 ...

  9. python 协程和异步的关系_python协程与异步协程

    在前面几个博客中我们一一对应解决了消费者消费的速度跟不上生产者,浪费我们大量的时间去等待的问题,在这里,针对业务逻辑比较耗时间的问题,我们还有除了多进程之外更优的解决方式,那就是协程和异步协程.在引入 ...

最新文章

  1. CentOS7下安装nvm
  2. c++:用顺序表实现简单的栈
  3. 基于python的开源商业软件套装 Odoo 简介
  4. Linux中常见命令和单词的缩写全称
  5. SonarQube6.2源码解析(四)
  6. Andriod之使用极光推送自定义消息打造个性的消息推送效果
  7. CSS快速学习7:定位锚点
  8. python中re模块_Python中re(正则表达式)模块学习
  9. 计算机专业知识面狭窄,计算机专业知识
  10. BZOJ1110 : [POI2007]砝码Odw
  11. Java-----jar反编译修改重新打包
  12. Html5 JumpStart学习笔记2:CSS Selectors and Style Properties
  13. WORD中使用类似苹果手机的字体Lucida Sans
  14. 鼠标单击变双击问题排查
  15. 最新最全的免费股票数据接口--沪深A股实时交易数据API接口(一)
  16. 计算机技术在生物学中的应用鲁东大学,生物科学与技术学院
  17. Stm32cubeIDE1.8 增加代码补齐
  18. 一个由两个长的如此相像的字引起的问题
  19. 项目实训工作总结(2)
  20. js中replace函数的使用

热门文章

  1. ERROR: Could not build wheels for numpy which use PEP 517 and cannot be installed directly
  2. python实现微信接龙统计
  3. 带七段式数码显示管的自动售卖机设计(verilog自学笔记之有限状态机的设计)
  4. 谷歌浏览器android插件开发工具,ARC Welder:在谷歌浏览器运行安卓APK
  5. Mac系统下连接阿里云并运行java程序
  6. Python转换表情符号 emoji
  7. 负载均衡获得真实源IP的6种方法
  8. sparksteaming的idea配置及入门程序
  9. 计算机电源不通电 维修,电脑忽然不通电,换了电源也不行,是哪里坏了?
  10. 鸿蒙手机卸载内置应用 adb连接