Java 异步编程导论

1、Java 异步编程导论

异步编程是可让程序并行运行的一种手段,其可让程序中的一个工做单元与主应用程序线程分开独立运行,而且等工做单元运行结束后通知主应用程序线程它的运行结果或者失败缘由。使用它有许多好处,例如改进的应用程序性能和减小用户等待时间等。前端

在平常开发中咱们常常会遇到这样的状况,就是须要异步的处理一些事情,而主线程不须要知道异步任务的结果,最多见的是在调用线程里面异步打日志,在高并发系统中为了避免让日志打印阻塞调用线程,会把日志设置为异步方式,也就是使用一个队列把日志打印异步化,这种状况下调用线程把日志任务放入队列后就继续去干本身的事情了,而再也不关心日志任务具体是何时入盘的。在Spring框架中提供的@Async注解就能够把一个任务异步化来进行处理,这个后面章节会具体讲解。golang

另外有时候咱们还须要开启异步任务执行后,在主线程等待异步任务的执行结果,这时候Future就排上用场了,好比线程A要作从数据库I和数据库II查询一条记录,而且把二者结果拼接起来做为前端展现使用,如线程A是同步调用两次查询,则整个过程耗时时间为访问数据库I的耗时加上访问数据库II的耗时,以下图:算法

640?wx_fmt=png若是为异步调用则能够在线程A内开启一个异步运行单元来从数据库I获取数据,而后线程A自己来从数据库II获取数据,而且等二者结果都返回后,在拼接二者结果,这时候整个过程耗时为max(线程A从数据库II获取数据耗时,异步运行单元从数据库I获取数据耗时),以下图:数据库

640?wx_fmt=png可见整个过程耗时有显著缩短,对于用户来讲页面响应时间会更短,对用户体验会更好,其中异步单元的执行通常是线程池中的线程。编程

使用Future确实能够获取异步任务的执行结果,可是获取其结果仍是会阻塞调用线程的,并无实现彻底异步化处理,在JDK8中提供了CompletableFuture来弥补了其缺点,实现了实际意义上的异步处理。网络

Java 8引入了lambdas和CompletableFuture,Lambdas容许编写简洁的回调,而CompletionStage接口和CompletableFuture类最终容许以非阻塞方式和基于推送的方式处理结果,其经过设置回调函数方式,让主线程完全解放出来,作本身的事情。多线程

Java 8还引入了Stream,它旨在有效地处理数据流(包括原始类型),这些数据流能够在没有延迟或不多延迟的状况下访问,其使用声明式编程让咱们能够写出可读性可维护性很强的代码,其结合CompletableFuture能够完美的实现异步编程。可是它是基于拉的,只能使用一次,缺乏与时间相关的操做,虽然能够执行并行计算,但没法指定要使用的线程池。它尚未设计用于处理延迟的操做,例如I / O操做。这就是Reactor或RxJava等Reactive API的用武之地。并发

Reactor或RxJava等反应性API也提供Java 8 Stream等运算符,但它们更适用于任何流序列(不只仅是集合),并容许定义一个转换操做的管道,该管道将应用于经过它的数据,这要归功于方便的流畅API和使用lambdas。它们旨在处理同步或异步操做,并容许您缓冲,合并,链接或对数据应用各类转换。app

另外对于网络传输来讲,同步调用时比较直截了当的,可是同步调用意味着当前发起请求的机器中的线程在远端机器返回结果前必须阻塞等待,这明显很浪费资源,好的作法应该是发起请求的机器发起调用线程发起请求后,注册一个回调函数,而后立刻返回去作其余事情,当远端把结果返回后在使用IO线程执行回调函数,也就是发起方实现了异步调用,调用线程不会被阻塞。框架

好比在使用rpc(远程过程调用)发起请时候,使用异步编程也能够提升系统的性能,好比咱们在一个线程A中经过rpc请求获取服务B和服务C的数据而后基于二者结果作一些事情。在同步rpc调用状况下,线程A须要调用服务B后须要等待服务B结果返回后,才能够对服务C发起调用,而后等服务C结果返回后才能够结合服务B和C的结果作一件事,以下图:

640?wx_fmt=png线程A同步获取服务B结果后,在同步调用服务C获取结果,可见在同步调用状况下线程A必须顺序的对多个服务请求进行调用。

而在异步调用状况下,当线程A调用服务B时候,服务B直接会返回一个异步的futureB对象,而后线程A能够继续访问服务C,服务C也会返回一个futureC对象,而后线程A就能够基于futureB和futureC来获取最终的返回结果,而后基于结果作一些事情,以下图:

640?wx_fmt=png可知异步调用状况下线程A能够并发的调用服务B和服务C,而再也不是顺序的,因为服务B和服务C是并发运行,因此相比线程A同步调用,线程A获取到服务B和服务C结果的时间会缩短不少(同步调用状况下耗时时间为服务B和服务C返回结果耗时的和,异步调用时候耗时为max(服务B耗时,服务C耗时)),后面章节咱们会以Dubbo框架为例其借助Netty的非阻塞异步API实现了服务消费端的异步调用。

在Web应用中Servlet占有一席之地,在Servlet3.0规范前,Servlet容器对Servlet的处理都是每一个请求对应一个线程这种1:1的模式进行处理的,每当来一个请求时候都会开启一个Servlet容器内的线程来进行处理,若是Servlet内处理比较耗时,则会把Servlet容器内线程使用耗尽,而后就不能再处理新的请求;Servlet3.0中则提供了异步处理的能力,让Servlet容器中的线程能够及时释放,具体Servlet业务处理逻辑是在业务本身线程池内来处理;虽然Servlet3.0规范让Servlet的执行变为了异步,可是其IO仍是阻塞式的,IO阻塞是说在Servlet处理请求时候从ServletInputStream中读取请求体时候是阻塞的,而咱们想要的是当数据已经就绪时候通知咱们去读取就能够了,由于这能够避免占用咱们本身的线程来进行阻塞读取,Servlet3.1规范则提供了非阻塞IO来解决这个问题。

虽然Servlet技术栈的不断发展实现了异步处理与非阻塞IO,可是其异步是不完全的,由于受制于Servlet规范自己,好比其规范是同步的(Filter,Servlet)或阻塞(getParameter,getPart)。因此新的使用少许线程和较少的硬件资源来处理并发非阻塞Web技术栈应运而生-WebFlux,其是与Servlet技术栈并行存在的一种新的技术,其基于JDK8函数式编程与Netty实现自然的异步、非阻塞处理。

另外为了更好的处理异步执行,一些框架也应运而生,好比高性能线程间消息传递库Disruptor,其经过为事件(events)预先分配内存、无锁CAS算法、缓冲行填充、两阶段协议提交来实现多线程并发的处理不一样的元素,从而实现高性能的异步处理(若是你对这些技术不熟悉的话,能够参考做者的《Java并发编程之美》一书);好比Akka其基于Actor模式实现了自然支持分布式的使用消息进行异步处理的服务。

一些新兴的语言对异步处理的支持能力让咱们忍不住称赞,golang就是其中之一,其经过goroutine与channel能够轻松的实现复杂的异步处理能力。

2、总结

异步、非阻塞、可编排的编程模型突破了传统编程模型限制,是如今乃至将来编程模型演变的趋势。

640?wx_fmt=gif

640?wx_fmt=gif

谈谈Golang并发编程

如何动态获取Dubbo服务提供方地址列表

使用指定IP调用Dubbo服务

高性能可扩展分布式RPC框架Dubbo-内核原理揭秘

K8s网络模型

Java并发编程视频分享-第一期

Java并发编程视频分享-第二期

Reactive(响应式)编程-Reactor


-----图书推荐-----

640?wx_fmt=png

-----关注本号-----

640?wx_fmt=png