第 15 章 CompletableFuture及反应式编程背后的概念

本章内容

  • 线程、Future以及推动Java支持更丰富的并发API的进化动力
  • 异步API
  • 从“线框与管道”的角度看并发计算
  • 使用CompletableFuture结合器动态地连接线框
  • 构成Java 9反应式编程Flow API基础的“发布–订阅”协议
  • 反应式编程和反应式系统

最近这些年,程序员们受两股潮流的影响,不断地反思他们编写代码的方式。第一种潮流与应用程序运行的硬件平台相关,而第二种潮流与应用程序的结构相关(尤其是它们之间如何交互)。第7章讨论过硬件的推陈出新对软件的影响。我们注意到,由于多核处理器的出现,提升应用程序执行速度最有效的方法是编写能充分利用多核处理器能力的软件。我们已经介绍过,你可以将一个大的任务分解成多个小型子任务,让每一个子任务以并行的方式相互独立地运行于多个核上。我们还介绍过如何使用fork/join框架(自Java 7引入)以及并行流(自Java 9引入)帮助你以更简单、更有效的方式完成一项任务,其效率甚至比直接使用线程还高。

第二种潮流反映了互联网应用对可用性日益增长的需求。譬如,过去的几年,采用微服务架构的应用越来越多。现在你的应用已经不再是单体型的结构,它被切分成了多个小型服务。协调这些小型服务必然需要更频繁的网络通信。类似地,越来越多的互联网服务现在都可以通过公有API访问,这些API通常由知名的服务提供商提供,譬如谷歌(位置信息)、Facebook(社交信息)和Twitter(新闻)。现在,我们已经极少开发一个完全独立的网络应用了。你的下一个网络应用很可能是一个聚合型应用(mashup),它使用来自多个数据源的内容,将它们聚集在一起,从而简化我们的生活。

假如你想要替你的法国用户构建一个网站来收集整理社交媒体对某个话题的观点。为了实现这个网站,你可以使用Facebook或者Twitter提供的API找到相关主题的热门评论,这些评论可能包含各种语言,而你需要使用你内部的算法找出最相关的条目。接着你可以使用谷歌翻译把这些评论翻译成法语,或者使用谷歌地图定位评论的作者。收集完所有这些信息后,就可以将它们展现在你的网站上了。

当然,采用这种架构也会受到一定的制约。当这些外部服务中的某些响应比较慢时,你肯定希望还能为你的用户提供部分数据,譬如以文本形式搭配一张带有问号的通用地图返回结果,而不是返回一个空白屏,直到地图服务器返回结果或者连接超时。图15-1展示了这种聚合型应用是如何与远程服务交互的。

第 15 章 CompletableFuture及反应式编程背后的概念 - 图1

图 15-1 一个典型的聚合型应用

为了实现这样一个应用,你往往需要跨互联网与多个网络服务通信。然而,你并不希望由于要等待远程服务的响应,阻塞现有的计算任务并白白浪费CPU中数十亿个宝贵的时钟周期。譬如,你不应该由于要等待Facebook数据的返回而停止对Twitter数据的处理。

这种情况也反映了多任务编程的另一面。第7章中讨论的fork/join框架以及并行流都是非常有价值的并行处理工具。它们将一个任务切分为多个子任务,并将这些子任务分配到不同的核、CPU或者机器上去以并行的方式执行。

与此相反,如果你处理并发而非并行任务,或者主要目标是在同一个CPU上执行多个松耦合的任务,你要考虑的是在等待(很可能是很长的一段时间)远程服务的结果或者查询数据库时,尽可能地让这些核都忙起来,从而最大化应用的吞吐量,尽量避免线程阻塞和浪费宝贵的计算资源。

为了解决这一问题,Java提供了两个主要的工具集。第一个就是在第16章和第17章中会学习的Future接口,尤其是Java 8中提供的CompletableFuture,这通常是既简单又有效的解决方案(详情请参考第16章)。最近Java 9又新增了对反应式编程的支持,它基于Flow API实现了所谓的“发布–订阅”协议,使用它能提供更加细粒度的程序控制(详情请参考第17章)。

图15-2说明了并发与并行这两种算法的差异。并发是一种编程属性(重叠地执行),即使在单核的机器上也可以执行,而并行是执行硬件的属性(同时执行)。

第 15 章 CompletableFuture及反应式编程背后的概念 - 图2

图 15-2 并发与并行

本章接下来的内容中会详细介绍Java新的CompletableFuture和Flow API的基本思想。

我们由Java并发的演化之路讲起,包括线程及其高层抽象,譬如线程池和Future(参见15.1节)。第7章中介绍的主要是“池类”(poolike)程序的并行。15.2节会介绍如何更好地发挥方法调用的并发性。15.3节会介绍将程序的各个部分看成通过管道通信的线框的一种图像表示法。15.4节和15.5节会分析Java 8和Java 9中的CompletableFuture和反应式编程原则。最后,15.6节会讨论反应式系统与反应式编程的区别。

阅读建议

本章内容几乎不涉及实战的Java代码。如果你目前只想了解如何编码,那么请跳过本章直接阅读第16章和第17章。另一方面,我们也都知道,实现陌生思想的代码很难理解。因此,本章会结合简单的函数和图表,从宏观角度介绍各种思想,譬如反应式编程Flow API背后的“发布–订阅”协议。

本章在讲述大多数概念时都会执行一些例子,譬如使用Java的各种并发特性计算像f(x)+ g(x)这样的表达式,并返回其结果,或者打印输出其结果——其中的f(x)g(x)都是比较耗时的计算任务。