当前位置: 首页 > news >正文

JDK之8后: 协程? 虚拟线程!!!

特性官方文档: https://openjdk.org/jeps/436

Java协程

近三十年来,Java 开发人员一直依赖线程作为并发服务器应用程序的构建块。每个方法中的每个语句都在线程内执行,并且由于 Java 是多线程的,因此多个执行线程同时发生。线程是Java的并发单位:一段与其他此类单元同时运行且在很大程度上独立于其他此类单元的顺序代码。每个线程都提供一个堆栈来存储局部变量和协调方法调用,以及出错时的上下文:异常是由同一线程中的方法抛出和捕获的,因此开发人员可以使用线程的堆栈跟踪来找出发生了什么。线程也是工具的核心概念:调试器逐步执行线程方法中的语句,分析器可视化多个线程的行为以帮助了解它们的性能。

Why

thread-per-request 风格

  1. 每一个请求一个线程跟容易理解、易于编程、易于调试和分析;
  2. 线程很大程度决定了并发,但线程无法无限增长,线程的摧毁&建立&切换耗时长;

使用异步风格提高可扩展性

  1. IO操作更多的时候复用线程才能高效利用线程
  2. 颗粒度更小
  3. 但是处理一个请求使用多个线程交替-栈追踪不行&调试器不发处理

虚拟线程的含义

  1. 便宜又充足
  2. 不用被池化
  3. 创建线程的代价变小
  4. 虚拟线程保留了可靠的 thread-per-request 风格
  5. 非常适合IO密集型

案例代码

案例一

轻松创建10000个线程

  1. 如果使用传统方案创建10000个线程会大概率崩溃
  2. 协程每秒处理10000个任务
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {IntStream.range(0, 10_000).forEach(i -> {executor.submit(() -> {Thread.sleep(Duration.ofSeconds(1));return i;});});
}  // executor.close() is called implicitly, and waits

案例二

io密集型的操作性能损耗更小,系统更加高效

void handle(Request request, Response response) {var url1 = ...var url2 = ...try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {var future1 = executor.submit(() -> fetchURL(url1));var future2 = executor.submit(() -> fetchURL(url2));response.send(future1.get() + future2.get());} catch (ExecutionException | InterruptedException e) {response.fail(e);}
}String fetchURL(URL url) throws IOException {try (var in = url.openStream()) {return new String(in.readAllBytes(), StandardCharsets.UTF_8);}
}

详解

独立的调度系统

线程需要被调度,即分配给在处理器内核上执行。对于作为操作系统线程实现的平台线程,JDK 依赖于操作系统中的调度程序。
虚拟线程,JDK 有自己的调度程序。JDK 的调度器不是直接将虚拟线程分配给处理器,而是将虚拟线程分配给平台线程,平台线程然后像往常一样由操作系统调度。
JDK 的虚拟线程调度程序是一种ForkJoinPool以 FIFO 模式运行的工作窃取。调度程序的并行度是可用于调度虚拟线程的平台线程数。默认情况下,它等于可用处理器的数量,但可以使用系统属性对其进行调整jdk.virtualThreadScheduler.parallelism。

也就是说,它默认并不是只基于一个线程,在一个线程上创建虚拟线程,而是处理器数量的基础线程数上创建虚拟线程

  1. 虽然可能在不同的线程但被屏蔽了
  2. 运行中获取的Thread.currentThread()始终是虚拟线程, 栈也与实际线程无关

执行虚拟线程

JDK 中的绝大多数阻塞操作都会卸载虚拟线程,释放它的载体和底层 OS 线程来承担新的工作。然而,JDK 中的一些阻塞操作并没有卸载虚拟线程,从而阻塞了它的载体和底层 OS 线程。这是因为操作系统级别(例如,许多文件系统操作)或 JDK 级别(例如,Object.wait())的限制。这些阻塞操作的实现将通过临时扩展调度程序的并行性来补偿操作系统线程的捕获。因此,调度程序中的平台线程数ForkJoinPool可能会暂时超过可用处理器的数量。可以使用系统属性调整调度程序可用的最大平台线程数jdk.virtualThreadScheduler.maxPoolSize。
有两种情况下虚拟线程无法在阻塞操作期间卸载,因为它被固定到其载体:

  1. 当它在块或方法内执行代码时synchronized,或者
  2. 当它执行一个native方法或一个外部函数时。

在案例中[10000次睡眠]执行结果可以预见的是不会阻塞或挂起线程,如果挂起线程可能就会出现睡眠10000秒的情况
也就是说这种阻塞操作会卸载虚拟线程(释放资源)执行其他;

也就是说有些操作会阻止虚拟线程的释放(不释放虚拟线程就会一直占用一个实际线程),从而导致虚拟线程的实际线程池超出上面说的核心数量, 比如调用native方法和Synchronized,;

初步测试下来wait&Synchronized是不会阻止卸载的

内存使用和与垃圾回收的交互

虚拟线程的堆栈作为堆栈块对象存储在 Java 的垃圾收集堆中。堆栈随着应用程序的运行而增长和收缩,既要提高内存效率,又要适应任意深度的堆栈(直到 JVM 配置的平台线程堆栈大小)。这种效率使大量虚拟线程成为可能,从而使服务器应用程序中每个请求线程样式的持续生存能力得以实现。

  1. 虚拟线程所需的栈帧布局比紧凑对象更浪费
  2. 虚拟线程不是GC根(也就是不是长活跃对象),所以只要没有被引用就会被回收
  3. 虚拟线程的堆栈达到区域大小的一半(可能小至 512KB),则StackOverflowError可能会抛出

核心: vs协程

核心区别

  1. 协程是基于一个线程,而虚拟线程是基于多个线程
  2. 虚拟线程需要注意多线程之间的同步&可见性问题, 协程基于单线程不需要注意

优势

基于多线程虽然要考虑多线的同步和可见性问题,但带了的是更高的性能水平

http://www.lryc.cn/news/45682.html

相关文章:

  • 体验 jeecg
  • 投稿指南【NO.13】计算机学会CCF推荐期刊和会议分享(人工智能)
  • 一份sql笔试
  • 交换瓶子
  • 二、Docker安装、启动、卸载、示例
  • 开心档之C++ STL 教程
  • Thread 类的基本用法
  • 2023.3.28 天梯赛训练赛补题(病毒溯源 , 龙龙送外卖 , 红色警报)
  • 917. 仅仅反转字母
  • Linux-Git
  • leetcode:2273. 移除字母异位词后的结果数组(python3解法)
  • 基于Python长时间序列遥感数据处理及在全球变化、物候提取、植被变绿与固碳分析、生物量估算与趋势分析等领域中的应用
  • 4.4---Spring框架之Spring事务(复习版本)
  • IP-Guard是否支持禁止客户端电脑卸载指定软件?
  • 系统图标形状overlayapk
  • 辅助编程coding的两种工具:Github Copilot、Cursor
  • MySQL5.7安装教程
  • ML@sklearn@ML流程Part3@AutomaticParameterSearches
  • Ubuntu22安装OpenJDK
  • 【数据库管理】②实例管理及数据库启动关闭
  • 【2023】Kubernetes之Pod与容器状态关系
  • LabVIEW阿尔泰PCIE 5654 例程与相关资料
  • spark2.4.4有哪些主要的bug
  • 信息学奥赛一本通 1347:【例4-8】格子游戏
  • acwing3417. 砝码称重
  • 生成式 AI:百度“文心一言”对标 ChatGPT?什么技术趋势促使 ChatGPT 火爆全网?
  • PCL 非线性最小二乘法拟合圆柱
  • 【设计模式】迪米特法则
  • CSS3笔试题精讲1
  • 交叉编译用于移植的Qt库