互联网保险经纪公司,seo网络优化推广,网站策划岗位职责,网页设计与网站开发的区别一 、认识异步编程
通常Java开发人员喜欢使用同步代码编写程序,因为这种请求(request)/响应(response)的方式比较简单#xff0c;并且比较符合编程人员的思维习惯;这种做法很好#xff0c;直到系统出现性能瓶颈#xff1b;在同步编程方式时由于每个线程同时只能发起一个请求…一 、认识异步编程
通常Java开发人员喜欢使用同步代码编写程序,因为这种请求(request)/响应(response)的方式比较简单并且比较符合编程人员的思维习惯;这种做法很好直到系统出现性能瓶颈在同步编程方式时由于每个线程同时只能发起一个请求并同步等待返回所以为了提高系统性能此时我们就需要引入更多的线程来实现并行化处理但是多线程下对共享资源进行访问时不可避免会引入资源争用和并发问题另外操作系统层面对线程的个数是有限制的不可能通过无限的增加线程数来提供系统性能最后使用同步阻塞的编程方式还会导致浪费资源比如发起网络IO请求时候调用线程就会处于同步阻塞等待响应结果的状态而这时候调用线程明明可以去做其他事情等网络IO响应结果返回后在对结果进行处理。
可见通过增加单机系统线程个数的并行编程方式并不是灵丹妙药通过编写异步、非阻塞的代码则可以使用相同的底层资源将执行切换到另一个活动任务然后在异步处理完成后在返回到当前线程进行继续处理从而提高系统性能。
异步编程是可以让程序并行运行的一种手段其可以让程序中的一个工作单元与主应用程序线程分开独立运行并且等工作单元运行结束后通知主应用程序线程它的运行结果或者失败原因。使用它有许多好处例如可以提高应用程序的性能和响应能力。
比如当调用线程使用异步方式发起网络IO请求后调用线程就不会同步阻塞等待响应结果而是在内存保存请求上下文后会马上返回后做其他事情等网络IO响应结果返回后在使用IO线程通知业务线程响应结果已经返回然后业务线程在对结果进行处理。可知异步调用方式提高了线程的利用率让系统有更多的线程资源来处理更多的请求。
比如在移动应用程序中在用户操作移动设备屏幕发起请求后如果是同步等待后台服务器返回结果则当后台服务操作非常耗时时就会造成用户看到移动设备屏幕冻结一直处理请求处理中在结果返回前用户不能操作移动设备的其他功能这对用户体验非常不好。而使用异步编程则当发起请求后调用线程会马上返回具体返回结果则会通过UI线程异步进行渲染而在这期间用户可以使用移动设备的其他功能。
二、 异步编程场景概述
在日常开发中我们经常会遇到这样的情况就是需要异步的处理一些事情而不需要知道异步任务的结果比如在调用线程里面异步打日志为了不让日志打印阻塞调用线程会把日志设置为异步方式。如下图1-2-1日志异步化打印使用一个内存队列把日志打印异步化使用单一线程来消费队列里面日志事件执行具体的日志落盘操作本质是一个多生产单消费模型这种情况下调用线程把日志任务放入队列后就继续去干自己的事情了而不再关心日志任务具体是什么时候入盘的 图 1-2-1 日志异步打印
在Java中每当我们需要执行异步任务的时候我们可以直接开启一个线程来实现也可以把异步任务封装为任务对象投递到线程池里面来执行在Spring框架中则提供了Async注解把一个任务异步化来进行处理这些内容会在后面章节具体讲解。
另外有时候我们还需要在主线程等待异步任务的执行结果这时候Future就排上用场了比如调用线程要等执行任务A执行完毕后在顺序执行任务B并且把两者结果拼接起来作为前端展示使用如果调用线程是同步调用两次查询如下图1-2-2同步调用则整个过程耗时时间为执行任务A的耗时加上执行任务B的耗时。 图1-2-2 同步调用
如果使用异步编程如下图1-2-3则可以在调用线程内开启一个异步运行单元来执行任务A开启异步运行单元后调用线程会马上返回一个Future对象futureB然后调用线程本身来执行任务B等任务B执行完毕后调用线程可以调用futureB的get方法获取任务A的执行结果最后在拼接两者结果。这时由于任务A和任务B是并行运行的所以整个过程耗时为max(调用线程执行任务B耗时异步运行单元执行任务A耗时。 图1-2-3 异步调用
可见整个过程耗时有显著缩短对于用户来说页面响应时间会更短对用户体验会更好其中异步单元的执行一般是线程池中的线程。
使用Future确实可以获取异步任务的执行结果但是获取其结果还是会阻塞调用线程的并没有实现完全异步化处理在JDK8中提供了CompletableFuture来弥补了其缺点。CompletableFuture类允许以非阻塞方式和基于通知的方式处理结果其通过设置回调函数方式让主线程彻底解放出来做自己的事情实现了实际意义上的异步处理
如下图1-2-4使用CompletableFuture时候当异步单元返回futureB后调用线程可以在其上调用whenComplete方法设置一个回调函数action,然后调用线程就会马上返回了等异步任务执行完毕后会使用异步线程来执行回调函数action而无需调用线程干预如果你对CompletableFuture不了解没关系后面章节我们会详细讲解这里你只需要知道其解决了传统Future的缺陷就可以了。 图1-2-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-2-5 图1-2-5 同步RPC调用
如上图1-2-5线程A同步获取服务B结果后在同步调用服务C获取结果可见在同步调用情况下业务执行语义比较清晰线程A顺序的对多个服务请求进行调用但是同步调用意味着当前发起请求的调用线程在远端机器返回结果前必须阻塞等待这明显很浪费资源。好的做法应该是发起请求的调用线程发起请求后注册一个回调函数然后马上返回去做其他事情当远端把结果返回后在使用IO线程执行回调函数。
那么如何实现异步调用在Java中NIO的出现让实现上面的功能变得简单而高性能异步、基于事件驱动的网络编程框架Netty的出现让我们从编写繁杂的Java NIO程序出解放出来了现在的RPC框架比如Dubbo底层网络通信就是基于Netty实现的;Netty框架将网络编程逻辑与业务逻辑处理分离开来其内部帮我们自动处理好网络与异步处理逻辑让我们专心写自己的业务处理逻辑Netty的异步非阻塞能力与CompletableFuture结合就可以轻松的实现网络请求的异步调用。
在执行RPC(远程过程调用)调用时候使用异步编程可以提高系统的性能如下图1-2-6在异步调用情况下当线程A调用服务B后马上会返回一个异步的futureB对象然后线程A可以在futureB上设置一个回调函数然后线程A可以继续访问服务C也会马上返回一个futureC对象然后线程A可以在futureC上设置一个回调函数 图1-2-6 RPC异步调用
如上图1-2-6可知异步调用情况下线程A可以并发的调用服务B和服务C而不再是顺序的由于服务B和服务C是并发运行所以相比线程A同步调用线程A获取到服务B和服务C结果的时间会缩短很多同步调用情况下耗时时间为服务B和服务C返回结果耗时的和异步调用时候耗时为max(服务B耗时服务C耗时另外这里可以借助CompletableFuture的能力等两次RPC调用都异步返回结果后做一件事情这时候调用流程如下图图1-2-7 图1-2-7 合并Rpc调用结果
如上图图1-2-7调用线程A首先发起服务B的远程调用然后马上返回一个futureB对象然后发起服务C的远程调用然后马上返回一个futureC对象最后调用线程A使用代码futureB.thenCombine(futureC,action)等futureB和futureC结果可用时候执行回调函数action这里我们只是简单的概述下基于Netty的异步非阻塞能力以及CompletableFuture的可编排能力我们可以实现功能很强大的异步编程能力后面章节我们会以Dubbo框架为例讲解其借助Netty的非阻塞异步API实现了服务消费端的异步调用。
其实有了CompletableFuture实现异步编程我们可以很自然的使用适配器来实现Reactive风格的编程当我们使用RxJava API时候我们只需要使用Flowable的一些函数转换CompletableFuture为Flowable对象即可这个我们在后面章节也会讲述。
上节讲解了网络请求中的RPC框架的异步请求其实还有一类也就是Web请求在Web应用中Servlet占有一席之地。在Servlet3.0规范前Servlet容器对Servlet的处理都是每个请求对应一个线程这种11的模式进行处理的如下图1-2-8每当来一个请求时候都会开启一个Servlet容器内的线程来进行处理如果Servlet内处理比较耗时则会把Servlet容器内线程使用耗尽然后容器就不能再处理新的请求。 图1-2-8 Servlet的阻塞处理模型
Servlet3.0规范中则提供了异步处理的能力让Servlet容器中的线程可以及时释放具体Servlet业务处理逻辑是在业务自己线程池内来处理虽然Servlet3.0规范让Servlet的执行变为了异步但是其IO还是阻塞式的IO阻塞是说在Servlet处理请求时候从ServletInputStream中读取请求体时候是阻塞的而我们想要的是当数据已经就绪时候通知我们去读取就可以了因为这可以避免占用我们自己的线程来进行阻塞读取Servlet3.1规范则提供了非阻塞IO来解决这个问题。
虽然Servlet技术栈的不断发展实现了异步处理与非阻塞IO但是其异步是不彻底的因为受制于Servlet规范本身比如其规范是同步的FilterServlet或阻塞getParametergetPart。所以新的使用少量线程和较少的硬件资源来处理并发的非阻塞Web技术栈应运而生-WebFlux,其是与Servlet技术栈并行存在的一种新的技术其基于JDK8函数式编程与Netty实现天然的异步、非阻塞处理这些我们在后面章节会具体介绍。
另外为了更好的处理异步编程降低我们异步编程的成本一些框架也应运而生比如高性能线程间消息传递库Disruptor其通过为事件events预先分配内存、无锁CAS算法、缓冲行填充、两阶段协议提交来实现多线程并发的处理不同的元素从而实现高性能的异步处理比如Akka其基于Actor模式实现了天然支持分布式的使用消息进行异步处理的服务比如高性能分布式消息中间件Apache RocketMetaQ用来实现应用间的异步解耦、流量削峰。
一些新兴的语言对异步处理的支持能力让我们忍不住称赞GoLang就是其中之一其通过语言层面内置的goroutine与channel可以轻松的实现复杂的异步处理能力。
《Java异步编程实战》)一书则是根据上述介绍的次序把内容划分了若干章节每章则具体展开讨论相应的异步编程技术。
三、 为何写作本书
异步编程是可以让程序并行运行的一种手段其可以让程序中的一个工作单元与主应用程序线程分开独立运行使用它有许多好处例如可以提高应用程序的性能和响应能力。
虽然Java中不同技术域提供了相应的异步编程技术但是对异步编程技术的描述散落到了不同技术域的技术文档中并没有一个统一的地方对这些技术进行梳理归纳。另外这些技术之间是什么关系各自的出现都是为了解决什么问题我们也很难找到资料来解释。
本书的出现则是为了打破这种局面本书旨在把Java中相关的异步编程技术进行归纳分类总结然后呈现给大家让大家可以有一个统一的地方来查看与探究。
四、本书特色
本书涵盖了Java中常见的异步编程场景这包含单JVM内的异步编程、以及跨主机通过网络通讯的远程过程调用的异步调用与异步处理、以及Web请求的异步处理等等。
本书在讲解Java中每种异步编程技术时都附有案例以便理论与实践进行结合。
本书在讲解每种异步编程技术时大多都会对其实现原理进行讲解以便让读者知其然也知其所以然。
本书对最近比较火的反应式编程以及WebFlux的使用与原理解析有一定深入的探索。
五、 业界推荐 原文链接 本文为阿里云原创内容未经允许不得转载。