响应式编程与协程

响应式编程与协程的比较

  • 响应式编程的弊端
  • 虚拟线程
    • Java线程
    • 内核线程的局限性
    • 传统线程池的demo
    • 虚拟线程的demo

响应式编程的弊端

前面用了几篇文章介绍了响应式编程,它更多的使用少量线程实现线程间解耦和异步的作用,如线程的Reactor模型,主要是把接收请求交给IO线程处理,然后业务的处理交给handler线程处理,它的弊端是①开发编码成本比较高,如下例demo:

Flux<Object> fluxDemo = Flux.create(sink -> {for (int i = 0; i < 3; i++) {sink.next(i);}//代表推送给下一个操作形成新流sink.complete();//过滤不等于0的数
}).log().filter(Predicate.not(Predicate.isEqual(0))).publishOn(Schedulers.single()).log();

发布者往下推送,相应于不断的回调,虽然说webFlux组件也尽量避免回调地狱,以及命令式编程的复杂性,②仍有使用函数式编程和事件驱动存在理解性上的难度,基于现有代码去改造,成本比较高,③响应式编程需要底层的很多第三方库支持,而这种第三方库是比不上JDK官方版本的代码质量,这三个原因是webflux没有大范围被应用的原因;

而且从线程角度,虽然响应式编程使用少量线程处理,在handler处理业务,也就是用户线程进行处理,如线程阻塞,cpu就需将调度切换到另一个线程,但仍然存在上下文切换的问题:

  1. 寄存器保存与恢复 线程切换时,操作系统需要保存当前线程的寄存器状态(如程序计数器、堆栈指针等),并恢复新线程的寄存器状态。这些操作涉及大量内存访问,增加了时间开销。

  2. 缓存失效 线程切换可能导致CPU缓存失效,新线程的数据和指令可能不在缓存中,需要从主存加载,这会显著增加延迟。

  3. 内存管理 切换线程时,操作系统可能需要更新内存管理单元(MMU)的页表,确保新线程能正确访问其内存空间。这一过程涉及TLB(转换后备缓冲区)的刷新,进一步增加延迟。

  4. 内核态与用户态切换 线程切换通常需要从用户态切换到内核态,执行完后再切换回用户态。这种模式切换涉及额外的开销。

  5. 调度开销 操作系统需要选择下一个要执行的线程,调度算法的复杂性也会影响切换速度,尤其是在高负载情况下。

  6. 锁与同步 在多线程环境中,切换可能涉及锁的获取与释放,若锁被其他线程持有,当前线程会被阻塞,进一步增加延迟。

  7. 中断处理 硬件中断可能触发线程切换,操作系统需要先处理中断,再执行切换,增加了额外开销。

  8. 上下文大小 线程的上下文越大,保存和恢复所需的时间越长,尤其是在寄存器多或内存占用大的情况下。

恰好JDK引入虚拟线程,从另外角度去解决并发问题。响应式编程和虚拟线程是竞品,在CPU密集型的业务场景中,响应式编程吞吐量是由于虚拟线程的,但在IO密集型中,虚拟线程吞吐量要高一些,所以与虚拟线程对比,spring webflux是弊大于利的,这也是响应式编程一直没有流行开来的原因;

虚拟线程

虚拟线程在Java 19中以预览模式引入,并在Java 21版本中正式成为标准功能,Java的虚拟线程参考了Golang这种协程的机制;

Java线程

内核线程直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身,这样操作系统内就有能力同时处理多件事,但程序一般不会直接使用内核线程,而是使用内核线程的一种高级接口——轻量级进程,轻量级进程就是通常意义上说的线程

每个轻量级进程都称为一个独立的调度单元,它是基于内核线程实现的,所以创建、析构和同步,都需要进行系统调用,前面也说系统调用是需要用户态切换到内核态的

其实在JDK1.2之前,Java线程就基于一种被称为“绿色线程”的用户线程实现,但从JDK1.3起,“主流”平台上的“主流”商用Java虚拟机的线程模型普遍都被换成基于操作系统原生线程模型来实现,即采用1:1的线程模型,以HotSpot为例,它的每一个Java线程都是直接映射到一个操作系统原生线程(就是内核线程)来实现的,而且中间没有额外的间接结构,所以HotSpot自己是不会去干涉线程调度的(但可以设置线程优先级给操作系统提供调度建议),全权交给底下的操作系统去处理,所以何时冻结或唤醒线程、该给线程分配多少处理器执行时间、该把线程安排给那个处理器核心去执行等,都是由操作系统完成的,也都是由操作系统全权决定的,例如只有cpu只有8个逻辑核,它就是创建8个原生线程,无论创建的线程池有多少个用户线程,都是调用轻量级进程接口让cpu切换着执行(至于cpu调度可参考之前的白话讲Linux进程如何被CPU调度)

内核线程的局限性

随着业务量的增加,QPS也要求越来越大,而Web应用的服务却要求每个接口的吞吐量保持大,这就要求每个服务都必须在极短时间内完成计算,1:1的内核线程模型是如今虚拟机线程实现的主流选择,但是这种映射到操作系统上的线程天然的缺陷是切换、调度成本高昂,系统能容纳的线程数量也是有限的;
用户线程的上下文切换是一种重量级的操作(上面有说上下文切换操作慢的原因),每遇到IO阻塞,就需要切换上下文,以及如果进行量化的话,那么如果不显示设置-Xss或-XX:ThreadStackSize,则在64位Linux上HotSpot的线程栈容量默认是1M,此外内核数据结构还额外消耗16Kb内存。

所以引入虚拟线程,也叫协程,它分为有栈和无栈协程序,通过在内存划分一片额外空间来模拟调用栈,只要其他“线程”中方法压栈、退栈时遵守规则,不破坏这片空间即可,这样多段代码执行时就会像相互缠绕着一样,非常形象。后来,操作系统开始提供多线程的支持,靠应用自己模拟多线程的做法自然是变少许多,而是演化为用户线程继续存在,也就说虚拟线程是在用户线程的基础上创建的,无论是创建和销毁都无需切换到内核态,性能自然高,而且一个协程的栈通常在几百个字节到几KB之间,所以Java虚拟机里线程池容量达到两百就已不小了,而支持协程的应用中,同时存在的协程数量可数以十万计;

传统线程池的demo

static class Task implements Runnable{CountDownLatch countDownLatch = null;Task(CountDownLatch countDownLatch){this.countDownLatch = countDownLatch;}@Overridepublic void run() {System.out.println(Thread.currentThread()+":开始");System.out.println(Thread.currentThread()+":虚拟线程在执行");try {Thread.sleep(1000);countDownLatch.countDown();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread()+":结束");}}
public static void main(String[] args) throws IOException, InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(3);ExecutorService executorService = Executors.newFixedThreadPool(1);long before = System.currentTimeMillis();executorService.execute(new Task(countDownLatch));executorService.execute(new Task(countDownLatch));executorService.execute(new Task(countDownLatch));countDownLatch.await();long after = System.currentTimeMillis() - before;System.out.println("耗费时间为"+after);System.in.read();
}

在这里插入图片描述
该demo中创建的线程池只有一个,提交的三个任务串行执行,耗费时间是三个任务执行时间总和;

虚拟线程的demo

public static void main(String[] args) throws IOException, InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(3);ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();long before = System.currentTimeMillis();executorService.execute(new Task(countDownLatch));executorService.execute(new Task(countDownLatch));executorService.execute(new Task(countDownLatch));long after = System.currentTimeMillis() - before;System.out.println("耗费时间为"+after);System.in.read();
}

在这里插入图片描述
创建虚拟线程,还是执行上面同一个Task任务,可以看到打印的线程名称都是ForkJoinPool-1,worker1、2、3共三个,也就是只创建了一个线程,在该线程的基础上创建了三个虚拟线程,执行时间不再是串行的3s,只是8ms;
可见对于IO密集型的任务,创建虚拟线程不仅可节省大量线程的内存,还有提高效率;

如有需要收藏的看官,顺便也用发财的小手点点赞哈,如有错漏,也欢迎各位在评论区评论!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

本地部署DeepSeek-R1模型(新手保姆教程)

背景 最近deepseek太火了&#xff0c;无数的媒体都在报道&#xff0c;很多人争相着想本地部署试验一下。本文就简单教学一下&#xff0c;怎么本地部署。 首先大家要知道&#xff0c;使用deepseek有三种方式&#xff1a; 1.网页端或者是手机app直接使用 2.使用代码调用API …

当WebGIS遇到智慧文旅-以长沙市不绕路旅游攻略为例

目录 前言 一、旅游数据组织 1、旅游景点信息 2、路线时间推荐 二、WebGIS可视化实现 1、态势标绘实现 2、相关位置展示 三、成果展示 1、第一天旅游路线 2、第二天旅游路线 3、第三天旅游路线 4、交通、订票、住宿指南 四、总结 前言 随着信息技术的飞速发展&…

93,【1】buuctf web [网鼎杯 2020 朱雀组]phpweb

进入靶场 页面一直在刷新 在 PHP 中&#xff0c;date() 函数是一个非常常用的处理日期和时间的函数&#xff0c;所以应该用到了 再看看警告的那句话 Warning: date(): It is not safe to rely on the systems timezone settings. You are *required* to use the date.timez…

如何在电脑上部署deepseek

由于免费的网页版经常显示服务器异常&#xff0c;并且每次打开网页麻烦&#xff0c;我们可以采用电脑部署的方法&#xff0c;V3和V2现在都很便宜&#xff0c;试了一下问了一下午问题也才0.1&#xff0c;而且现在注册就送14元&#xff0c;心动不如行动&#xff0c;快来薅羊毛&am…

SmartPipe完成新一轮核心算法升级

1. 增加对低质量轴段的修正 由于三维图纸导出造成某些轴段精度较差&#xff0c;部分管路段的轴线段不满足G1连续&#xff0c;SmartPipe采用算法对这种情况进行了修正&#xff0c;保证轴段在一定精度范围内光滑连续。 2. 优化对中文路径的处理 SmartPipeBatch批处理版本优化…

2.3学习总结

今天做了下上次测试没做出来的题目&#xff0c;作业中做了一题&#xff0c;看了下二叉树&#xff08;一脸懵B&#xff09; P2240&#xff1a;部分背包问题 先求每堆金币的性价比&#xff08;价值除以重量&#xff09;&#xff0c;将这些金币由性价比从高到低排序。 对于排好…

四川正熠法律咨询有限公司正规吗可信吗?

在纷繁复杂的法律环境中&#xff0c;寻找一家值得信赖的法律服务机构是每一个企业和个人不可或缺的需求。四川正熠法律咨询有限公司&#xff0c;作为西南地区备受瞩目的法律服务提供者&#xff0c;以其专注、专业和高效的法律服务&#xff0c;成为众多客户心中的首选。 正熠法…

【leetcode练习·二叉树拓展】快速排序详解及应用

本文参考labuladong算法笔记[拓展&#xff1a;快速排序详解及应用 | labuladong 的算法笔记] 1、算法思路 首先我们看一下快速排序的代码框架&#xff1a; def sort(nums: List[int], lo: int, hi: int):if lo > hi:return# 对 nums[lo..hi] 进行切分# 使得 nums[lo..p-1]…

FPGA学习篇——开篇之作

今天正式开始学FPGA啦&#xff0c;接下来将会编写FPGA学习篇来记录自己学习FPGA 的过程&#xff01; 今天是大年初六&#xff0c;简单学一下FPGA的相关概念叭叭叭&#xff01; 一&#xff1a;数字系统设计流程 一个数字系统的设计分为前端设计和后端设计。在我看来&#xff0…

DeepSeek R1 简易指南:架构、本地部署和硬件要求

DeepSeek 团队近期发布的DeepSeek-R1技术论文展示了其在增强大语言模型推理能力方面的创新实践。该研究突破性地采用强化学习&#xff08;Reinforcement Learning&#xff09;作为核心训练范式&#xff0c;在不依赖大规模监督微调的前提下显著提升了模型的复杂问题求解能力。 技…

Vue3学习笔记-模板语法和属性绑定-2

一、文本插值 使用{ {val}}放入变量&#xff0c;在JS代码中可以设置变量的值 <template><p>{{msg}}</p> </template> <script> export default {data(){return {msg: 文本插值}} } </script> 文本值可以是字符串&#xff0c;可以是布尔…

Android学习19 -- 手搓App

1 前言 之前工作中&#xff0c;很多时候要搞一个简单的app去验证底层功能&#xff0c;Android studio又过于重型&#xff0c;之前用gradle&#xff0c;被版本匹配和下载外网包折腾的堪称噩梦。所以搞app都只有找应用的同事帮忙。一直想知道一些简单的app怎么能手搓一下&#x…

深度解读 Docker Swarm

一、引言 随着业务规模的不断扩大和应用复杂度的增加,容器集群管理的需求应运而生。如何有效地管理和调度大量的容器,确保应用的高可用性、弹性伸缩和资源的合理分配,成为了亟待解决的问题。Docker Swarm 作为 Docker 官方推出的容器集群管理工具,正是在这样的背景下崭露头…

centos stream 9 安装 libstdc++-static静态库

yum仓库中相应的镜像源没有打开&#xff0c;libstdc-static在CRB这个仓库下&#xff0c;但是查看/etc/yum.repos.d/centos.repo&#xff0c;发现CRB镜像没有开启。 解决办法 如下图开启CRB镜像&#xff0c; 然后执行 yum makecache yum install glibc-static libstdc-static…

玉米苗和杂草识别分割数据集labelme格式1997张3类别

数据集格式&#xff1a;labelme格式(不包含mask文件&#xff0c;仅仅包含jpg图片和对应的json文件) 图片数量(jpg文件个数)&#xff1a;1997 标注数量(json文件个数)&#xff1a;1997 标注类别数&#xff1a;3 标注类别名称:["corn","weed","Bean…

Docker入门篇(Docker基础概念与Linux安装教程)

目录 一、什么是Docker、有什么作用 二、Docker与虚拟机(对比) 三、Docker基础概念 四、CentOS安装Docker 一、从零认识Docker、有什么作用 1.项目部署可能的问题&#xff1a; 大型项目组件较多&#xff0c;运行环境也较为复杂&#xff0c;部署时会碰到一些问题&#xff1…

图像处理之图像灰度化

目录 1 图像灰度化简介 2 图像灰度化处理方法 2.1 均值灰度化 2.2 经典灰度化 2.3 Photoshop灰度化 2.4 C语言代码实现 3 演示Demo 3.1 开发环境 3.2 功能介绍 3.3 下载地址 参考 1 图像灰度化简介 对于24位的RGB图像而言&#xff0c;每个像素用3字节表示&#xff0…

《MPRnet》学习笔记

paper&#xff1a;2102.02808 GitHub&#xff1a;swz30/MPRNet: [CVPR 2021] Multi-Stage Progressive Image Restoration. SOTA results for Image deblurring, deraining, and denoising. 目录 摘要 1、介绍 2、相关工作 2.1 单阶段方法 2.2 多阶段方法 2.3 注意力机…

Spark的基本概念

个人博客地址&#xff1a;Spark的基本概念 | 一张假钞的真实世界 编程接口 RDD&#xff1a;弹性分布式数据集&#xff08;Resilient Distributed Dataset &#xff09;。Spark2.0之前的编程接口。Spark2.0之后以不再推荐使用&#xff0c;而是被Dataset替代。Dataset&#xff…

自动驾驶---两轮自行车的自主导航

1 背景 无人驾驶汽车最早出现在DARPA的比赛中&#xff0c;从那个时刻开始&#xff0c;逐渐引起全球学者的注意&#xff0c;于是从上个世纪开始各大高校院所开始了无人汽车的研发。直到这两年&#xff0c;无人驾驶汽车才开始走进寻常百姓家&#xff0c;虽然目前市面上的乘用车还…