Statistics
33
Views
0
Downloads
0
Donations
Uploader

高宏飞

Shared on 2025-12-14
Support
Share

Author翟陆续

No description

Tags
No tags
ISBN: 7111642996
Language: 中文
File Format: PDF
File Size: 2.4 MB
Support Statistics
¥.00 · 0times
Text Preview (First 20 pages)
Registered users can read the full content for free

Register as a Gaohf Library member to read the complete e-book online for free and enjoy a better reading experience.

(This page has no text content)
Java核心技术系列 Java异步编程实战 翟陆续 著 ISBN:978-7-111-64299-2 本书纸版由机械工业出版社于2020年出版,电子版由华章分社(北京 华章图文信息有限公司,北京奥维博世图书发行有限公司)全球范围 内制作与发行。 版权所有,侵权必究 客服热线:+ 86-10-68995265 客服信箱:service@bbbvip.com 官方网址:www.hzmedia.com.cn 新浪微博 @华章数媒 微信公众号 华章电子书(微信号:hzebook)
目录 前言 第1章 认识异步编程 1.1 异步编程概念与作用 1.2 异步编程场景 1.3 总结 第2章 显式使用线程和线程池实现异步编程 2.1 显式使用线程实现异步编程 2.2 显式使用线程池实现异步编程 2.2.1 如何显式使用线程池实现异步编程 2.2.2 线程池ThreadPoolExecutor原理剖析 2.3 总结 第3章 基于JDK中的Future实现异步编程 3.1 JDK中的Future 3.2 JDK中的FutureTask 3.2.1 FutureTask概述 3.2.2 FutureTask的类图结构 3.2.3 FutureTask的run()方法 3.2.4 FutureTask的get()方法 3.2.5 FutureTask的cancel(boolean mayInterruptIfRunning) 方法 3.2.6 FutureTask的局限性 3.3 JDK中的CompletableFuture 3.3.1 CompletableFuture概述 3.3.2 显式设置CompletableFuture结果 3.3.3 基于CompletableFuture实现异步计算与结果转换 3.3.4 多个CompletableFuture进行组合运算 3.3.5 异常处理 3.3.6 CompletableFuture概要原理 3.4 JDK8 Stream&CompletableFuture 3.4.1 JDK8 Stream 3.4.2 当Stream遇见CompletableFuture 3.5 总结 第4章 Spring框架中的异步执行 4.1 Spring中对TaskExecutor的抽象
4.2 如何在Spring中使用异步执行 4.2.1 使用TaskExecutor实现异步执行 4.2.2 使用注解@Async实现异步执行 4.3 @Async注解异步执行原理 4.4 总结 第5章 基于反应式编程实现异步编程 5.1 反应式编程概述 5.2 Reactive Streams规范 5.3 基于RxJava实现异步编程 5.4 基于Reactor实现异步编程 5.5 总结 第6章 Web Servlet的异步非阻塞处理 6.1 Servlet概述 6.2 Servlet 3.0提供的异步处理能力 6.3 Servlet 3.1提供的非阻塞IO能力 6.4 Spring Web MVC的异步处理能力 6.4.1 基于DeferredResult的异步处理 6.4.2 基于Callable实现异步处理 6.5 总结 第7章 Spring WebFlux的异步非阻塞处理 7.1 Spring WebFlux概述 7.2 Reactive编程&Reactor库 7.3 WebFlux服务器 7.4 WebFlux的并发模型 7.5 WebFlux对性能的影响 7.6 WebFlux的编程模型 7.6.1 WebFlux注解式编程模型 7.6.2 WebFlux函数式编程模型 7.7 WebFlux原理浅尝 7.7.1 Reactor Netty概述 7.7.2 WebFlux服务器启动流程 7.7.3 WebFlux一次服务调用流程 7.8 WebFlux的适用场景 7.9 总结 第8章 高性能异步编程框架和中间件 8.1 异步、基于事件驱动的网络编程框架——Netty
8.1.1 Netty概述 8.1.2 Netty的线程模型 8.1.3 TCP半包与粘包问题 8.1.4 基于Netty与CompletableFuture实现RPC异步调用 8.2 高性能RPC框架——Apache Dubbo 8.2.1 Apache Dubbo概述 8.2.2 Dubbo的异步调用 8.2.3 Dubbo的异步执行 8.3 高性能线程间消息传递库——Disruptor 8.3.1 Disruptor概述 8.3.2 Disruptor的特性详解 8.3.3 基于Disruptor实现异步编程 8.4 异步、分布式、基于消息驱动的框架——Akka 8.4.1 Akka概述 8.4.2 传统编程模型存在的问题 8.4.3 Actor模型解决了传统编程模型的问题 8.4.4 基于Akka实现异步编程 8.5 高性能分布式消息框架——Apache RocketMQ 8.5.1 Apache RocketMQ概述 8.5.2 基于Apache RocketMQ实现系统间异步解耦 8.6 总结 第9章 Go语言的异步编程能力 9.1 Go语言概述 9.2 Go语言的线程模型 9.2.1 一对一模型 9.2.2 多对一模型 9.2.3 多对多模型 9.2.4 Go语言的线程模型 9.3 goroutine与channel 9.3.1 goroutine 9.3.2 channel 9.3.3 构建管道实现异步编程 9.4 总结
前言 为何写作本书 异步编程是可以让程序并行运行的一种手段,可以让程序中的一 个工作单元与主应用程序线程分开独立运行,进而提高应用程序的性 能和响应能力等。 虽然Java为不同技术域提供了相应的异步编程技术,但是这些异 步编程技术被散落到不同技术域的技术文档中,没有一个统一的文档 对其进行梳理归纳。另外这些技术之间是什么关系,各自的出现都是 为了解决什么问题,我们也很难找到相关资料来解释。 本书的出现则是为了打破这种局面,旨在对Java中相关的异步编 程技术进行归纳总结,为读者提供一个统一文档来查阅、参考。 本书特色 本书涵盖了Java中常见的异步编程场景,包括单JVM内的异步编 程、跨主机通过网络通信的远程过程调用的异步调用与异步处理,以 及Web请求的异步处理等。 本书在讲解Java中每种异步编程技术时都附有案例,以理论与实 践相结合的方式,帮助读者更好地掌握相关内容。 书中在讲解每种异步编程技术时多会对其实现原理进行讲解,让 读者知其然也知其所以然。 对于最近比较热门的反应式编程以及WebFlux的使用与原理解析, 本书也有一定的深入探索。 本书读者对象
本书适用于有一定Java编程基础,并对Java并发编程、Java异步 编程、反应式编程感兴趣的读者。 如何阅读本书 对于初学者,建议按照本书编写的章节顺序进行学习,因为本书 是按照从易到难的顺序编写的,并且每章都有一些代码示例供大家动 手实践,以便加深理解。如果你对Java并发编程与异步编程有一定的 了解,那么可以直接从目录查看感兴趣的章节进行学习。本书共分为9 章,内容概述如下: 第1章主要讲解异步编程的概念和作用,以及在日常开发中都有哪 些异步编程场景。 第2章讲解最基础的显式使用线程和线程池来实现异步编程的方 法,也分析了它们目前存在的缺点。 第3章内容比较丰富,主要讲解JDK中的各种Future,包括如何使 用Future实现异步编程及其内部实现原理,然后讲解了如何结合JDK8 Stream和Future实现异步编程。 第4章讲解Spring框架中提供的异步执行能力,包括在Spring中如 何对TaskExecutor进行抽象,如何使用注解@Async实现异步编程,以 及其内部实现原理。 第5章讲解比较热门的反应式编程相关的内容,包括什么是反应式 编程,如何使用反应式编程规范的库RxJava和Reactor实现异步编程。 第6章讲解Web Servlet的异步非阻塞处理,包括Servlet 3.0规范 是如何提供异步处理能力的,Servlet 3.1规范是如何解决IO阻塞问题 的,以及如何在Spring MVC进行异步处理。 第7章讲解与Servlet技术栈并行存在的、由Spring5.0提出的 Spring WebFlux异步非阻塞处理,包括Spring WebFlux的由来、 Spring WebFlux的并发模型、两种编程模型,以及如何使用Spring WebFlux来进行服务开发、Spring WebFlux内部的实现原理。
第8章简要介绍了业界为方便实现异步编程而设计的一些框架和中 间件,比如异步基于事件驱动的网络编程框架Netty,高性能RPC框架 Apache Dubbo,高性能线程间消息传递库Disruptor,异步、分布式、 基于事件驱动的编程框架Akka和高性能分布式消息框架Apache RocketMQ。 第9章介绍新兴的Go语言是如何从语言层面提供强大的异步编程能 力的。 资源和勘误 有需要的读者可以到https://github.com/zhailuxu/async- program-demo 下载本书的demo资源,由于笔者水平有限,如果你在阅 读本书时发现错误,可以把错误信息提交到华章网站 (www.hzbook.com )。 致谢 首先要感谢机械工业出版社杨福川老师的团队,他们拥有丰富的 出版经验,在本书的命名以及目录结构调整上,给出了很多中肯的建 议;其次要感谢我的家人,感谢他们对我的鼓励与支持,让我有充分 的时间来写作本书。
第1章 认识异步编程 本章主要介绍异步编程的概念与作用,Java中异步编程的场景以 及不同异步编程场景应使用什么技术来实现。
1.1 异步编程概念与作用 通常Java开发人员喜欢使用同步代码编写程序,因为这种请求 (request)/响应(response)的方式比较简单,并且比较符合编程 人员的思维习惯;这种做法很好,直到系统出现性能瓶颈。在使用同 步编程方式时,由于每个线程同时只能发起一个请求并同步等待返 回,所以为了提高系统性能,此时我们就需要引入更多的线程来实现 并行化处理。但是多线程下对共享资源进行访问时,不可避免会引入 资源争用和并发问题;另外,操作系统层面对线程的个数是有限制 的,不可能通过无限增加线程数来提供系统性能;而且,使用同步阻 塞的编程方式还会浪费资源,比如发起网络IO请求时,调用线程就会 处于同步阻塞等待响应结果的状态,而这时候调用线程明明可以去做 其他事情,等网络IO响应结果返回后再对结果进行处理。 可见通过增加单机系统线程个数的并行编程方式并不是“灵丹妙 药”。通过编写异步、非阻塞的代码,则可以使用相同的底层资源将 执行切换到另一个活动任务,然后在异步处理完成后再返回到当前线 程继续处理,从而提高系统性能。 异步编程是可以让程序并行运行的一种手段,其可以让程序中的 一个工作单元与主应用程序线程分开独立运行,并且在工作单元运行 结束后,会通知主应用程序线程它的运行结果或者失败原因。使用异 步编程可以提高应用程序的性能和响应能力等。 比如当调用线程使用异步方式发起网络IO请求后,调用线程就不 会同步阻塞等待响应结果,而是在内存保存请求上下文后,马上返回 去做其他事情,等网络IO响应结果返回后再使用IO线程通知业务线程 响应结果已经返回,由业务线程对结果进行处理。可见,异步调用方 式提高了线程的利用率,让系统有更多的线程资源来处理更多的请 求。 比如在移动应用程序中,在用户操作移动设备屏幕发起请求后, 如果是同步等待后台服务器返回结果,则当后台服务操作非常耗时 时,就会造成用户看到移动设备屏幕冻结(一直处于请求处理中), 在结果返回前,用户不能操作移动设备的其他功能,这对用户体验非
常不好。而使用异步编程时,当发起请求后,调用线程会马上返回, 具体返回结果会通过UI线程异步进行渲染,且在这期间用户可以使用 移动设备的其他功能。
1.2 异步编程场景 在日常开发中我们经常会遇到这样的情况,即需要异步地处理一 些事情,而不需要知道异步任务的结果。比如在调用线程里面异步打 日志,为了不让日志打印阻塞调用线程,会把日志设置为异步方式。 如图1-1所示的日志异步化打印,使用一个内存队列把日志打印异步 化,然后使用单一消费线程异步处理内存队列中的日志事件,执行具 体的日志落盘操作(本质是一个多生产单消费模型),在这种情况 下,调用线程把日志任务放入队列后会继续执行其他操作,而不再关 心日志任务具体是什么时候入盘的。 图1-1 日志异步打印
在Java中,每当我们需要执行异步任务时,可以直接开启一个线 程来实现,也可以把异步任务封装为任务对象投递到线程池中来执 行。在Spring框架中提供了@Async注解把一个任务异步化来进行处 理,具体会在后面章节详细讲解。 有时候我们还需要在主线程等待异步任务的执行结果,这时候 Future就派上用场了。比如调用线程要等任务A执行完毕后再顺序执行 任务B,并且把两者的执行结果拼接起来供前端展示使用,如果调用线 程是同步调用两次任务(如图1-2所示),则整个过程耗时为执行任务 A的耗时加上执行任务B的耗时。 图1-2 同步调用 如果使用异步编程(如图1-3所示),则可以在调用线程内开启一 个异步运行单元来执行任务A,开启异步运行单元后调用线程会马上返 回一个Future对象(futureB),然后调用线程本身来执行任务B,等 任务B执行完毕后,调用线程可以调用futureB的get()方法获取任务A 的执行结果,最后再拼接两者的结果。这时由于任务A和任务B是并行 运行的,所以整个过程耗时为max(调用线程执行任务B的耗时,异步运 行单元执行任务A的耗时)。
图1-3 异步调用 可见整个过程耗时显著缩短,对于用户来说,页面响应时间缩 短,用户体验会更好,其中异步单元的执行一般是由线程池中的线程 执行。 使用Future确实可以获取异步任务的执行结果,但是获取其结果 还是会阻塞调用线程的,并没有实现完全异步化处理,所以在JDK8中 提供了CompletableFuture来弥补其缺点。CompletableFuture类允许 以非阻塞方式和基于通知的方式处理结果,其通过设置回调函数方 式,让主线程彻底解放出来,实现了实际意义上的异步处理。 如图1-4所示,使用CompletableFuture时,当异步单元返回 futureB后,调用线程可以在其上调用whenComplete方法设置一个回调 函数action,然后调用线程就会马上返回,等异步任务执行完毕后会 使用异步线程来执行回调函数action,而无须调用线程干预。如果你 对CompletableFuture不了解,没关系,后面章节我们会详细讲解,这 里你只需要知道其解决了传统Future的缺陷就可以了。
图1-4 CompletableFuture异步执行 JDK8还引入了Stream,旨在有效地处理数据流(包括原始类 型),其使用声明式编程让我们可以写出可读性、可维护性很强的代 码,并且结合CompletableFuture完美地实现异步编程。但是它产生的 流只能使用一次,并且缺少与时间相关的操作(例如RxJava中基于时 间窗口的缓存元素),虽然可以执行并行计算,但无法指定要使用的 线程池。同时,它也没有设计用于处理延迟的操作(例如RxJava中的 defer操作),所以Reactor、RxJava等Reactive API就是为了解决这 些问题而生的。 Reactor、RxJava等反应式API也提供Java 8 Stream的运算符,但 它们更适用于流序列(不仅仅是集合),并允许定义一个转换操作的 管道,该管道将应用于通过它的数据(这要归功于方便的流畅API和 Lambda表达式的使用)。Reactive旨在处理同步或异步操作,并允许 你对元素进行缓冲(buffer)、合并(merge)、连接(join)等各种 转换。 上面我们讲解了单JVM内的异步编程,那么对于跨网络的交互是否 也存在异步编程范畴呢?对于网络请求来说,同步调用是比较直截了 当的。比如我们在一个线程A中通过RPC请求获取服务B和服务C的数 据,然后基于两者的结果做一些事情。在同步调用情况下,线程A需要 调用服务B,然后同步等待服务B结果返回后,才可以对服务C发起调 用,等服务C结果返回后才可以结合服务B和C的结果执行其他操作。
如图1-5所示,线程A同步获取服务B的结果后,再同步调用服务C 获取结果,可见在同步调用情况下业务执行语义比较清晰,线程A顺序 地对多个服务请求进行调用;但是同步调用意味着当前发起请求的调 用线程在远端机器返回结果前必须阻塞等待,这明显很浪费资源。好 的做法应该是在发起请求的调用线程发起请求后,注册一个回调函 数,然后马上返回去执行其他操作,当远端把结果返回后再使用IO线 程或框架线程池中的线程执行回调函数。 图1-5 同步RPC调用 那么如何实现异步调用?在Java中NIO的出现让实现上面的功能变 得简单,而高性能异步、基于事件驱动的网络编程框架Netty的出现让 我们从编写繁杂的Java NIO程序中解放出来,现在的RPC框架,比如 Dubbo底层网络通信,就是基于Netty实现的。Netty框架将网络编程逻 辑与业务逻辑处理分离开来,在内部帮我们自动处理好网络与异步处 理逻辑,让我们专心写自己的业务处理逻辑,而Netty的异步非阻塞能 力与CompletableFuture结合则可以轻松地实现网络请求的异步调用。 在执行RPC(远程过程调用)调用时,使用异步编程可以提高系统 的性能。如图1-6所示,在异步调用情况下,当线程A调用服务B后,会 马上返回一个异步的futureB对象,然后线程A可以在futureB上设置一 个回调函数;接着线程A可以继续访问服务C,也会马上返回一个 futureC对象,然后线程A可以在futureC上设置一个回调函数。
图1-6 RPC异步调用 如图1-6可知,在异步调用情况下,线程A可以并发地调用服务B和 服务C,而不再是顺序的。由于服务B和服务C是并发运行,所以相比同 步调用,线程A获取到服务B和服务C结果的时间会缩短很多(同步调用 情况下的耗时为服务B和服务C返回结果耗时的和,异步调用情况下耗 时为max(服务B耗时,服务C耗时))。另外,这里可以借助 CompletableFuture的能力等两次RPC调用都异步返回结果后再执行其 他操作,这时候调用流程如图1-7所示。
图1-7 合并RPC调用结果 如图1-7所示,调用线程A首先发起服务B的远程调用,会马上返回 一个futureB对象,然后发起服务C的远程调用,也会马上返回一个 futureC对象,最后调用线程A使用代码 futureB.thenCombine(futureC,action)等futureB和futureC结果可 用时执行回调函数action。这里我们只是简单概述下基于Netty的异步 非阻塞能力以及Completable-Future的可编排能力,基于这些能力, 我们可以实现功能很强大的异步编程能力。在后面章节,我们会以 Dubbo框架为例讲解其借助Netty的非阻塞异步API实现服务消费端的异 步调用。 其实,有了CompletableFuture实现异步编程,我们可以很自然地 使用适配器来实现Reactive风格的编程。当我们使用RxJava API时, 只需要使用Flowable的一些函数转换CompletableFuture为Flowable对 象即可,这个我们在后面章节也会讲述。 上节讲解了网络请求中RPC框架的异步请求,其实还有一类,也就 是Web请求,在Web应用中Servlet占有一席之地。在Servlet3.0规范 前,Servlet容器对Servlet的处理都是每个请求对应一个线程这种1:1 的模式进行处理的(如图1-8所示),每当收到一个请求,都会开启一 个Servlet容器内的线程来进行处理,如果Servlet内处理比较耗时, 则会把Servlet容器内线程使用耗尽,然后容器就不能再处理新的请求 了。 图1-8 Servlet的阻塞处理模型
Servlet 3.0规范中则提供了异步处理的能力,让Servlet容器中 的线程可以及时释放,具体Servlet业务处理逻辑是在业务自己的线程 池内来处理;虽然Servlet 3.0规范让Servlet的执行变为了异步,但 是其IO还是阻塞式的。IO阻塞是说在Servlet处理请求时,从 ServletInputStream中读取请求体时是阻塞的,而我们想要的是当数 据就绪时直接通知我们去读取就可以了,因为这可以避免占用我们自 己的线程来进行阻塞读取,好在Servlet 3.1规范提供了非阻塞IO来解 决这个问题。 虽然Servlet技术栈的不断发展实现了异步处理与非阻塞IO,但是 其异步是不彻底的,因为受制于Servlet规范本身,比如其规范是同步 的(Filter,Servlet)或阻塞的(getParameter,getPart)。所以 新的使用少量线程和较少的硬件资源来处理并发的非阻塞Web技术栈应 运而生——WebFlux,其是与Servlet技术栈并行存在的一种新技术, 基于JDK8函数式编程与Netty实现天然的异步、非阻塞处理,这些我们 在后面章节会具体介绍。 为了更好地处理异步编程,降低异步编程的成本,一些框架也应 运而生,比如高性能线程间消息传递库Disruptor,其通过为事件 (event)预先分配内存、无锁CAS算法、缓冲行填充、两阶段协议提 交来实现多线程并发地处理不同的元素,从而实现高性能的异步处 理。比如Akka基于Actor模式实现了天然支持分布式的使用消息进行异 步处理的服务;比如高性能分布式消息中间件Apache RocketMetaQ实 现了应用间的异步解耦、流量削峰。 一些新兴的语言对异步处理的支持能力让我们忍不住称赞,Go语 言就是其中之一,其通过语言层面内置的goroutine与channel可以轻 松实现复杂的异步处理能力。 以上就是本书要讨论的内容,根据上述介绍的顺序,书中将内容 划分为若干章节,每章则具体展开讨论相应的异步编程技术。
1.3 总结 本章我们首先概要介绍了异步编程的概念与作用,让大家对异步 编程有一个大致的了解;然后讲解了Java中异步编程的场景,让大家 通过实际场景案例进一步了解异步编程是什么,以及不同异步编程场 景应使用什么技术来实现。
The above is a preview of the first 20 pages. Register to read the complete e-book.