异步编程 - 01 漫谈异步编程发展史

news2024/11/26 20:50:42

文章目录

  • 同步编程vs异步编程
  • 异步编程小故事
    • 单JVM
      • 异步地处理一些事情,而不需要知道异步任务的结果
      • 主线程等待异步任务的执行结果
      • Future确实可以获取异步任务的执行结果,但是获取其结果还是会阻塞调用线程的,并没有实现完全异步化处理 --> CompletableFuture
      • Reactor、RxJava等反应式API
    • RPC框架的异步请求
      • 同步RPC调用
      • RPC异步调用
      • 合并RPC调用结果
    • Web
      • Servlet的阻塞处理模型
      • Servlet 3.0 / 3. 1 非阻塞IO
      • WebFlux
    • 异步编程框架
    • 新兴的语言对异步处理的支持能力

在这里插入图片描述


同步编程vs异步编程

同步编程的优点和问题:

  • 同步编程, 简单且符合思维习惯,但在性能瓶颈时需要引入更多线程以实现并行化处理。
  • 多线程访问共享资源引入了资源争用和并发问题。
  • 操作系统限制了线程数量,无法无限增加线程以提高性能。
  • 同步阻塞编程浪费资源,例如在网络IO请求中,线程会阻塞等待响应,浪费了其它可用资源。

异步编程的优点:

  • 异步编程允许程序并行运行,将工作单元与主应用程序线程分开独立运行,并在完成后通知主应用程序线程结果或失败原因。
  • 异步编程提高应用程序性能和响应能力。
  • 通过异步方式发起网络IO请求,调用线程不会同步阻塞,可以在等待响应时执行其他任务,提高线程利用率。
  • 异步编程可以提供更好的用户体验,允许用户在请求处理中执行其他操作,而不会冻结应用界面。

异步编程小故事

单JVM

异步地处理一些事情,而不需要知道异步任务的结果

比如在调用线程里面异步打日志,为了不让日志打印阻塞调用线程,会把日志设置为异步方式。如图 所示的日志异步化打印,使用一个内存队列把日志打印异步化,然后使用单一消费线程异步处理内存队列中的日志事件,执行具体的日志落盘操作(本质是一个多生产单消费模型),在这种情况下,调用线程把日志任务放入队列后会继续执行其他操作,而不再关心日志任务具体是什么时候入盘的。

在这里插入图片描述

在Java中,每当我们需要执行异步任务时,可以直接开启一个线程来实现,也可以把异步任务封装为任务对象投递到线程池中来执行。

在Spring框架中提供了@Async注解把一个任务异步化来进行处理。


主线程等待异步任务的执行结果

这时候Future就派上用场了。比如调用线程要等任务A执行完毕后再顺序执行任务B,并且把两者的执行结果拼接起来供前端展示使用,如果调用线程是同步调用两次任务 ,则整个过程耗时为执行任务A的耗时加上执行任务B的耗时。

【同步调用】
在这里插入图片描述


【异步调用】

如果使用异步编程 ,则可以在调用线程内开启一个异步运行单元来执行任务A,开启异步运行单元后调用线程会马上返回一个Future对象(futureB),然后调用线程本身来执行任务B,等任务B执行完毕后,调用线程可以调用futureB的get()方法获取任务A的执行结果,最后再拼接两者的结果。

在这里插入图片描述
这时由于任务A和任务B是并行运行的,所以整个过程耗时为max(调用线程执行任务B的耗时,异步运行单元执行任务A的耗时)。

可见整个过程耗时显著缩短,对于用户来说,页面响应时间缩短,用户体验会更好,其中异步单元的执行一般是由线程池中的线程执行。


Future确实可以获取异步任务的执行结果,但是获取其结果还是会阻塞调用线程的,并没有实现完全异步化处理 --> CompletableFuture

使用Future确实可以获取异步任务的执行结果,但是获取其结果还是会阻塞调用线程的,并没有实现完全异步化处理,所以在JDK8中提供了CompletableFuture来弥补其缺点。CompletableFuture类允许以非阻塞方式和基于通知的方式处理结果,其通过设置回调函数方式,让主线程彻底解放出来,实现了实际意义上的异步处理。

【CompletableFuture异步执行】

在这里插入图片描述


Reactor、RxJava等反应式API

JDK8还引入了Stream,旨在有效地处理数据流(包括原始类型),其使用声明式编程让我们可以写出可读性、可维护性很强的代码,并且结合CompletableFuture完美地实现异步编程。

但是它产生的流只能使用一次,并且缺少与时间相关的操作(例如RxJava中基于时间窗口的缓存元素),虽然可以执行并行计算,但无法指定要使用的线程池。

同时,它也没有设计用于处理延迟的操作(例如RxJava中的defer操作),所以Reactor、RxJava等Reactive API就是为了解决这些问题而生的。

Reactor、RxJava等反应式API也提供Java 8 Stream的运算符,但它们更适用于流序列(不仅仅是集合),并允许定义一个转换操作的管道,该管道将应用于通过它的数据(这要归功于方便的流畅API和Lambda表达式的使用)。

Reactive旨在处理同步或异步操作,并允许你对元素进行缓冲(buffer)、合并(merge)、连接(join)等各种转换。


RPC框架的异步请求

上面讲解了单JVM内的异步编程,那么对于跨网络的交互是否也存在异步编程范畴呢?

同步RPC调用

对于网络请求来说,同步调用是比较直截了当的。比如我们在一个线程A中通过RPC请求获取服务B和服务C的数据,然后基于两者的结果做一些事情。在同步调用情况下,线程A需要调用服务B,然后同步等待服务B结果返回后,才可以对服务C发起调用,等服务C结果返回后才可以结合服务B和C的结果执行其他操作。

在这里插入图片描述

线程A同步获取服务B的结果后,再同步调用服务C获取结果,可见在同步调用情况下业务执行语义比较清晰,线程A顺序地对多个服务请求进行调用

RPC异步调用

但是同步调用意味着当前发起请求的调用线程在远端机器返回结果前必须阻塞等待,这明显很浪费资源。好的做法应该是在发起请求的调用线程发起请求后,注册一个回调函数,然后马上返回去执行其他操作,当远端把结果返回后再使用IO线程或框架线程池中的线程执行回调函数。

那么如何实现异步调用?在Java中NIO的出现让实现上面的功能变得简单,而高性能异步、基于事件驱动的网络编程框架Netty的出现让我们从编写繁杂的Java NIO程序中解放出来,现在的RPC框架,比如Dubbo底层网络通信,就是基于Netty实现的。Netty框架将网络编程逻辑与业务逻辑处理分离开来,在内部帮我们自动处理好网络与异步处理逻辑,让我们专心写自己的业务处理逻辑,而Netty的异步非阻塞能力与CompletableFuture结合则可以轻松地实现网络请求的异步调用。

在执行RPC(远程过程调用)调用时,使用异步编程可以提高系统的性能。如所示,在异步调用情况下,当线程A调用服务B后,会马上返回一个异步的futureB对象,然后线程A可以在futureB上设置一个回调函数;接着线程A可以继续访问服务C,也会马上返回一个futureC对象,然后线程A可以在futureC上设置一个回调函数。

在这里插入图片描述

在异步调用情况下,线程A可以并发地调用服务B和服务C,而不再是顺序的。由于服务B和服务C是并发运行,所以相比同步调用,线程A获取到服务B和服务C结果的时间会缩短很多(同步调用情况下的耗时为服务B和服务C返回结果耗时的和,异步调用情况下耗时为max(服务B耗时,服务C耗时))。


合并RPC调用结果

这里可以借助CompletableFuture的能力等两次RPC调用都异步返回结果后再执行其他操作,这时候调用流程如下图所示。

在这里插入图片描述

如图所示,调用线程A首先发起服务B的远程调用,会马上返回一个futureB对象,然后发起服务C的远程调用,也会马上返回一个futureC对象,最后调用线程A使用代码futureB.thenCombine(futureC,action)等futureB和futureC结果可用时执行回调函数action。这里我们只是简单概述下基于Netty的异步非阻塞能力以及Completable-Future的可编排能力,基于这些能力,我们可以实现功能很强大的异步编程能力。

其实,有了CompletableFuture实现异步编程,我们可以很自然地使用适配器来实现Reactive风格的编程。当我们使用RxJava API时,只需要使用Flowable的一些函数转换CompletableFuture为Flowable对象即可 。


Web

上面讲解了网络请求中RPC框架的异步请求,其实还有一类,也就是Web请求

Servlet的阻塞处理模型

,在Web应用中Servlet占有一席之地。在Servlet3.0规范前,Servlet容器对Servlet的处理都是每个请求对应一个线程这种1:1的模式进行处理的 ,每当收到一个请求,都会开启一个Servlet容器内的线程来进行处理,如果Servlet内处理比较耗时,则会把Servlet容器内线程使用耗尽,然后容器就不能再处理新的请求了。

在这里插入图片描述

Servlet 3.0 / 3. 1 非阻塞IO

Servlet 3.0规范中则提供了异步处理的能力,让Servlet容器中的线程可以及时释放,具体Servlet业务处理逻辑是在业务自己的线程池内来处理;

虽然Servlet 3.0规范让Servlet的执行变为了异步,但是其IO还是阻塞式的。IO阻塞是说在Servlet处理请求时,从ServletInputStream中读取请求体时是阻塞的,而我们想要的是当数据就绪时直接通知我们去读取就可以了,因为这可以避免占用我们自己的线程来进行阻塞读取,好在Servlet 3.1规范提供了非阻塞IO来解决这个问题.


WebFlux

虽然Servlet技术栈的不断发展实现了异步处理与非阻塞IO,但是其异步是不彻底的,因为受制于Servlet规范本身,比如其规范是同步的(Filter,Servlet)或阻塞的(getParameter,getPart)。

所以新的使用少量线程和较少的硬件资源来处理并发的非阻塞Web技术栈应运而生——WebFlux,其是与Servlet技术栈并行存在的一种新技术,基于JDK8函数式编程与Netty实现天然的异步、非阻塞处理


异步编程框架

为了更好地处理异步编程,降低异步编程的成本,一些框架也应运而生,

比如高性能线程间消息传递库Disruptor,其通过为事件(event)预先分配内存、无锁CAS算法、缓冲行填充、两阶段协议提交来实现多线程并发地处理不同的元素,从而实现高性能的异步处理。

比如Akka基于Actor模式实现了天然支持分布式的使用消息进行异步处理的服务;比如高性能分布式消息中间件Apache RocketMetaQ实现了应用间的异步解耦、流量削峰。


新兴的语言对异步处理的支持能力

Go语言就是其中之一,其通过语言层面内置的goroutine与channel可以轻松实现复杂的异步处理能力。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/978454.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LabVIEW应用开发——LabVIEW2019保姆级介绍、安装、第一个程序

一、前言 LabVIEW是一种程序开发环境,由美国国家仪器(NI)公司研制开发,类似于C和BASIC开发环境,但是LabVIEW与其他计算机语言的显著区别是:其他计算机语言都是采用基于文本的语言产生代码,而Lab…

JavaSE-日志

世上只有想不通的人,没有走不通的路。 文章目录 1. 日志概述2. Logback日志框架3. Logback快速入门4. Logback配置详解输出位置设置日志级别设置 1. 日志概述 通过日志可以查看程序运行的过程和详情。 输出语句的弊端: 信息只能展示在控制台。不能将其记…

java读取服务器数据包并下载至本地目录

jsch包如果没有的话,可评论联系我,我私发给你,或者通过https://mvnrepository.com/artifact/com.jcraft/jsch/0.1.55进行下载,添加至工程目录 package com.hbisdt.dqbasic.modular.util;import com.jcraft.jsch.*;import java.i…

OpenShift 4 - 利用 OpenShift 的 OAuth Proxy 实现应用身份认证

《OpenShift / RHEL / DevSecOps 汇总目录》 说明:本文已经在 OpenShift 4.13 的环境中验证 文章目录 部署测试应用只有认证用户才能访问只有有权的用户才能访问使用 ServiceAccount 访问参考 说明: 本文需要集群中除了管理员外还有一个一般用户。另外除…

Leetcode 1486.数组异或操作

给你两个整数,n 和 start 。 数组 nums 定义为:nums[i] start 2*i(下标从 0 开始)且 n nums.length 。 请返回 nums 中所有元素按位异或(XOR)后得到的结果。 示例 1: 输入:n 5, …

NPM 常用命令(四)

目录 1、npm diff 1.1 描述 1.2 过滤文件 1.3 配置 diff diff-name-only diff-unified diff-ignore-all-space diff-no-prefix diff-src-prefix diff-dst-prefix diff-text global tag workspace workspaces include-workspace-root 2、npm dist-tag 2.1 常…

79 # koa 相应结果设置

返回的类型可能是文件流,或者是对象的等方式,需要我们对 body 的类型进行判断在返回。 判断是否是 string 或者 buffer 、流、对象 if (typeof body "string" || Buffer.isBuffer(body)) {res.end(body); } else if (body instanceof Strea…

大模型参数高效微调技术原理综述(三)-P-Tuning、P-Tuning v2

随着,ChatGPT 迅速爆火,引发了大模型的时代变革。然而对于普通大众来说,进行大模型的预训练或者全量微调遥不可及。由此,催生了各种参数高效微调技术,让科研人员或者普通开发者有机会尝试微调大模型。 因此&#xff0c…

面试题速记:JavaScript有哪些数据类型,它们的区别是?

JavaScript有哪些数据类型,它们的区别? JavaScript共有八种数据类型,分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。 其中 Symbol 和 BigInt 是ES6 中新增的数据类型: ●Symbol 代表创建后独一无二…

vue3 element - plus 安装使用教程

下边是安装教程 element - plus 是针对 vue3 开发 一个 Vue 3 UI 框架 | Element Plus (element-plus.org)https://element-plus.org/zh-CN/ 安装 element - plus ui 库 # 选择一个你喜欢的包管理器# NPM $ npm install element-plus --save# Yarn $ yarn add elemen…

python 学习笔记(4)—— webdriver 自动化操作浏览器(基础操作)

安装 web driver: 使用 driver 前,需要下载与浏览器版本相对应的 driver。如要在 Chrome 浏览器上操作,就要下载Chrome Driver。 几个常用浏览器的参考和下载地址: Edge Driver:https://developer.microsoft.com/en…

C++内存区堆和栈

在C中,内存分成5个区,堆、栈、自由存储区、全局/静态存储区和常量存储区。 栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。 堆,就是那些…

《Linux从练气到飞升》No.21 Linux简单实现一个shell

🕺作者: 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux菜鸟刷题集 😘欢迎关注:👍点赞🙌收藏✍️留言 🏇码字不易,你的👍点赞🙌收藏❤️关注对我真的…

​​​​​​​光伏智慧设施休息区

随着人们环保意识的日益增强,光伏智慧景观渐渐出现在大众的视野,成为低碳城镇建设的新景观、新亮点。打造光伏智慧设施休息区,既能满足游客休息纳凉,还能设置手机相机等充电区域。此设备组还可提供夜间照明灯、音乐广播、多媒体广…

flask中的操作数据库的插件Flask-SQLAlchemy

1、ORM 框架 Web 开发中,一个重要的组成部分便是数据库了。Web 程序中最常用的莫过于关系型数据库了,也称 SQL 数据库。另外,文档数据库(如 mongodb)、键值对数据库(如 redis)近几年也逐渐在 w…

普通平衡树 Splay

Splay 简介 Splay(伸展树),又叫做分裂树,是一种自调整形式的二叉查找树,满足二叉查找树的性质:一个节点左子树的所有节点的权值,均小于这个节点的权值。且其右子树所有节点的权值,均…

IEC 62368-1:2023(第4版)《音频视频、信息和通信设备 第1部分安全要求》标准发布,IEC 62368-1第四版标准更新与变化

2023年05月26日,IEC 62368-1:2023 《音频视频、信息和通信设备 第1部分安全要求》第4版标准正式发布,2023年08月18日,IECEE又发布了TRF模板,为新版标准的实施和应用做好了准备. 下载地址 : https://download.csdn.net…

【个人博客系统网站】项目的发布 · 通过公网IP访问我们的网站 · 思考总结

【JavaEE】进阶 个人博客系统(6) 文章目录 【JavaEE】进阶 个人博客系统(6)1. 项目发布1.1 后端代码修改1.1.1 数据库密码1.1.2 端口号修改1.1.3 文件保存地址修改1.1.4 静态资源映射修改 1.2 云服务器1.2.1 建库建表1.2.2 必要…

算法通关村第十三关——溢出问题处理模板

前言 溢出问题是面试当中输出涉及到数字的一个需要特别注意的地方,典型的题目有三个:数字反转,将字符串转成数字和回文数。 1.整数反转 力扣7题,给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。…

Vue + Element UI 前端篇(十五):嵌套外部网页

Vue Element UI 实现权限管理系统 前端篇(十五):嵌套外部网页 嵌套外部网页 在有些时候,我们需要在我们的内容栏主区域显示外部网页。如查看服务端提供的SQL监控页面,接口文档页面等。 这个时候就要求我们的导航菜…