JDK21最终版协程实现之虚拟线程(上)

简介: JDK21最终版协程实现之虚拟线程

1 全新并发编程模式

JDK9 后的版本你觉得没必要折腾,我也认可,但是JDK21有必要关注。因为 JDK21 引入全新的并发编程模式。

一直沽名钓誉的GoLang吹得最厉害的就是协程了。JDK21 中就在这方面做了很大的改进,让Java并发编程变得更简单一点,更丝滑一点。


之前写过JDK21 Feature。Virtual Threads、Scoped Values、Structured Concurrency就是针对多线程并发编程的几个功能。。


2 发展历史

虚拟线程是轻量级线程,极大地减少了编写、维护和观察高吞吐量并发应用的工作量。

虚拟线程是由JEP 425提出的预览功能,并在JDK 19中发布,JDK 21中最终确定虚拟线程,以下是根据开发者反馈从JDK 20中的变化:

  • 现在,虚拟线程始终支持线程本地变量。与在预览版本中允许的不同,现在不再可能创建不能具有线程本地变量的虚拟线程。对线程本地变量的有保障支持确保了许多现有库可以不经修改地与虚拟线程一起使用,并有助于将以任务为导向的代码迁移到使用虚拟线程
  • 直接使用Thread.Builder API创建的虚拟线程(而不是通过Executors.newVirtualThreadPerTaskExecutor()创建的虚拟线程)现在默认情况下也会在其生命周期内进行监控,并且可以通过描述在"观察虚拟线程"部分中的新线程转储来观察。

基于协程的线程,与其他语言中的协程有相似之处,也有不同。虚拟线程是依附于主线程的,如果主线程销毁了,虚拟线程也不复存在。

3 目标

  • 使采用简单的 thread-per-request 模式编写的服务器应用程序,能以接近最佳的硬件利用率扩展
  • 使利用java.lang.Thread API的现有代码能在最小更改下采用虚拟线程
  • 通过现有的JDK工具轻松进行虚拟线程的故障排除、调试和分析

4 非目标

  • 不是删除传统的线程实现,也不是悄悄将现有应用程序迁移到使用虚拟线程
  • 不是改变Java的基本并发模型
  • 不是在Java语言或Java库中提供新的数据并行构造。Stream API仍是处理大型数据集的首选方式。

5 动机

Java开发人员在近30年来一直依赖线程作为并发服务端应用程序的构建块。每个方法中的每个语句都在一个线程内执行,并且由于Java是多线程,多个线程同时执行。


线程是Java的并发单元:它是一段顺序代码,与其他这样的单元并发运行,很大程度上是独立的。每个线程提供一个堆栈来存储局部变量和协调方法调用及在出现问题时的上下文:异常由同一线程中的方法抛出和捕获,因此开发可使用线程的堆栈跟踪来查找发生了啥。


线程也是工具的核心概念:调试器逐步执行线程方法中的语句,分析工具可视化多个线程的行为,以帮助理解它们的性能。

6 thread-per-request模式

服务器应用程序通常处理彼此独立的并发用户请求,因此将一个线程专用于处理整个请求在逻辑上是合理的。这种模式易理解、易编程,且易调试和分析,因为它使用平台的并发单元来表示应用程序的并发单元。

服务器应用程序的可扩展性受到Little定律约束,该定律关联延迟、并发性和吞吐量:对给定的请求处理持续时间(即延迟),应用程序同时处理的请求数量(并发性)必须与到达速率(吞吐量)成比例增长。如一个具有平均延迟为50ms的应用程序,通过同时处理10个请求实现每秒处理200个请求的吞吐量。为使该应用程序扩展到每秒处理2000个请求吞吐量,它要同时处理100个请求。如每个请求在其持续时间内都使用一个线程(因此使用一个os线程),那在其他资源(如CPU或网络连接)耗尽前,线程数量通常成为限制因素。JDK对线程的当前实现将应用程序的吞吐量限制在远低于硬件支持水平的水平。即使线程进行池化,仍然发生,因为池化可避免启动新线程的高成本,但并不会增加总线程数。

7 使用异步模式提高可扩展性

一些开发人员为了充分利用硬件资源,已经放弃了采用"thread-per-request"的编程风格,转而采用"共享线程"。这种方式,请求处理的代码在等待I/O操作完成时会将其线程返回给一个线程池,以便该线程可以为其他请求提供服务。这种对线程的精细共享,即只有在执行计算时才保持线程,而在等待I/O时释放线程,允许高并发操作而不消耗大量线程资源。虽然它消除了由于os线程有限而导致的吞吐量限制,但代价高:它需要一种异步编程风格,使用一组专门的I/O方法,这些方法不会等待I/O操作完成,而是稍后通过回调通知其完成。


在没有专用线程情况下,开发须将请求处理逻辑分解为小阶段,通常编写为lambda表达式,然后使用API(如CompletableFuture或响应式框架)将它们组合成顺序管道。因此,他们放弃语言的基本顺序组合运算符,如循环和try/catch块。


异步风格中,请求的每个阶段可能在不同线程执行,每个线程交错方式运行属于不同请求的阶段。这对于理解程序行为产生了深刻的影响:堆栈跟踪提供不了可用的上下文,调试器无法逐步执行请求处理逻辑,分析器无法将操作的成本与其调用者关联起来。使用Java的流API在短管道中处理数据时,组合lambda表达式是可管理的,但当应用程序中的所有请求处理代码都必须以这种方式编写时,会带来问题。这种编程风格与Java平台不符,因为应用程序的并发单位——异步管道——不再是平台的并发单位。

8 通过虚拟线程保持 thread-per-request 编程风格

为了在保持与平台和谐的情况下使应用程序能扩展,应努力通过更高效方式实现线程,以便它们可更丰富存在。os无法更高效实现操作系统线程,因为不同编程语言和运行时以不同方式使用线程堆栈。然而,JRE可通过将大量虚拟线程映射到少量操作系统线程来实现线程的伪装丰富性,就像os通过将大型虚拟地址空间映射到有限的物理内存一样,JRE可通过将大量虚拟线程映射到少量操作系统线程来实现线程的伪装丰富性。


虚拟线程是java.lang.Thread一个实例,不与特定os线程绑定。相反,平台线程是java.lang.Thread的一个实例,以传统方式实现,作为包装在操作系统线程周围的薄包装。


采用 thread-per-request 编程风格的应用程序,可在整个请求的持续时间内在虚拟线程中运行,但虚拟线程仅在它在CPU上执行计算时才会消耗os线程。结果与异步风格相同,只是它是透明实现:当在虚拟线程中运行的代码调用java.* API中的阻塞I/O操作时,运行时会执行非阻塞的os调用,并自动暂停虚拟线程,直到可稍后恢复。对Java开发,虚拟线程只是便宜且几乎无限丰富的线程。硬件利用率接近最佳,允许高并发,因此实现高吞吐量,同时应用程序与Java平台及其工具的多线程设计保持和谐一致。

9 虚拟线程的含义

虚拟线程成本低且丰富,因此永远都不应被池化:每个应用程序任务应该创建一个新的虚拟线程。因此,大多数虚拟线程将是短暂的,且具有浅层次的调用栈,执行的操作可能只有一个HTTP客户端调用或一个JDBC查询。相比之下,平台线程是重量级且代价昂贵,因此通常必须池化。它们倾向于具有较长的生命周期,具有深层次调用栈,并在许多任务间共享。


总之,虚拟线程保留了与Java平台设计和谐一致的可靠的 thread-per-request 编程风格,同时最大限度地利用硬件资源。使用虚拟线程无需学习新概念,尽管可能需要放弃为应对当前线程成本高昂而养成的习惯。虚拟线程不仅将帮助应用程序开发人员,还将帮助框架设计人员提供与平台设计兼容且不会牺牲可伸缩性的易于使用的API。


目录
相关文章
|
3月前
|
消息中间件 并行计算 安全
进程、线程、协程
【10月更文挑战第16天】进程、线程和协程是计算机程序执行的三种基本形式。进程是操作系统资源分配和调度的基本单位,具有独立的内存空间,稳定性高但资源消耗大。线程是进程内的执行单元,共享内存,轻量级且并发性好,但同步复杂。协程是用户态的轻量级调度单位,适用于高并发和IO密集型任务,资源消耗最小,但不支持多核并行。
59 1
|
3月前
|
算法 NoSQL Java
Springboot3新特性:GraalVM Native Image Support和虚拟线程(从入门到精通)
这篇文章介绍了Spring Boot 3中GraalVM Native Image Support的新特性,提供了将Spring Boot Web项目转换为可执行文件的步骤,并探讨了虚拟线程在Spring Boot中的使用,包括如何配置和启动虚拟线程支持。
192 9
Springboot3新特性:GraalVM Native Image Support和虚拟线程(从入门到精通)
|
3月前
|
存储 消息中间件 人工智能
进程,线程,协程 - 你了解多少?
本故事采用简洁明了的对话方式,尽洪荒之力让你在轻松无负担的氛围中,稍微深入地理解进程、线程和协程的相关原理知识
50 2
进程,线程,协程 - 你了解多少?
|
3月前
|
Java 关系型数据库 MySQL
如何用java的虚拟线程连接数据库
本文介绍了如何使用Java虚拟线程连接数据库,包括设置JDK版本、创建虚拟线程的方法和使用虚拟线程连接MySQL数据库的示例代码。
78 6
如何用java的虚拟线程连接数据库
|
3月前
|
消息中间件 并行计算 安全
进程、线程、协程
【10月更文挑战第15天】进程、线程和协程是操作系统中三种不同的执行单元。进程是资源分配和调度的基本单位,每个进程有独立的内存空间;线程是进程内的执行路径,共享进程资源,切换成本较低;协程则更轻量,由用户态调度,适合处理高并发和IO密集型任务。进程提供高隔离性和安全性,线程支持高并发,协程则在资源消耗和调度灵活性方面表现优异。
75 2
|
3月前
|
Dubbo Java 应用服务中间件
剖析Tomcat线程池与JDK线程池的区别和联系!
剖析Tomcat线程池与JDK线程池的区别和联系!
177 0
剖析Tomcat线程池与JDK线程池的区别和联系!
|
3月前
|
监控 数据可视化 Java
如何使用JDK自带的监控工具JConsole来监控线程池的内存使用情况?
如何使用JDK自带的监控工具JConsole来监控线程池的内存使用情况?
|
3月前
|
监控 Java API
|
3月前
|
Java 应用服务中间件 测试技术
Java21虚拟线程:我的锁去哪儿了?
【10月更文挑战第8天】
58 0
|
4月前
|
Java
安装JDK18没有JRE环境的解决办法
安装JDK18没有JRE环境的解决办法
409 3