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。


目录
相关文章
|
24天前
|
Oracle Java API
虚拟线程:Java的新利器?
作者通过本文梳理了自己对虚拟线程的理解,顺便捋一捋Java线程的过去、现在和未来。ps:写这篇文章的时候,Java 19刚刚发布,而现在Java 21已经正式GA了,不过虚拟线程的API和底层实现并没有什么变化。
|
25天前
|
Java 程序员 调度
【JAVA 并发秘籍】进程、线程、协程:揭秘并发编程的终极武器!
【8月更文挑战第25天】本文以问答形式深入探讨了并发编程中的核心概念——进程、线程与协程,并详细介绍了它们在Java中的应用。文章不仅解释了每个概念的基本原理及其差异,还提供了实用的示例代码,帮助读者理解如何在Java环境中实现这些并发机制。无论你是希望提高编程技能的专业开发者,还是准备技术面试的求职者,都能从本文获得有价值的见解。
33 1
|
1月前
|
Go 调度 开发者
[go 面试] 深入理解进程、线程和协程的概念及区别
[go 面试] 深入理解进程、线程和协程的概念及区别
|
5天前
|
监控 数据可视化 Java
使用JDK自带的监控工具JConsole来监控线程池的内存使用情况
使用JDK自带的监控工具JConsole来监控线程池的内存使用情况
|
26天前
|
缓存 Java 调度
【Java 并发秘籍】线程池大作战:揭秘 JDK 中的线程池家族!
【8月更文挑战第24天】Java的并发库提供多种线程池以应对不同的多线程编程需求。本文通过实例介绍了四种主要线程池:固定大小线程池、可缓存线程池、单一线程线程池及定时任务线程池。固定大小线程池通过预设线程数管理任务队列;可缓存线程池能根据需要动态调整线程数量;单一线程线程池确保任务顺序执行;定时任务线程池支持周期性或延时任务调度。了解并正确选用这些线程池有助于提高程序效率和资源利用率。
33 2
|
27天前
|
Java 调度 开发者
Java 神秘新成员 —— 虚拟线程究竟是什么?它又能解开哪些编程痛点之谜?
【8月更文挑战第23天】Java虚拟线程是一种轻量级执行线程,由Java运行时管理,相较于传统操作系统线程,其创建和管理成本更低。基于用户模式线程概念,Java应用程序无需依赖OS即可实现高度并发。虚拟线程数量可远超传统线程,有效提升多核处理器利用率和并发性能。它解决了传统Java线程模型中创建成本高、调度开销大及I/O阻塞等问题,极大提高了程序的并发性和响应速度。
18 1
|
1月前
|
调度 Android开发 开发者
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
37 4
|
2月前
|
消息中间件 程序员 调度
如何区分进程、线程和协程?看这篇就够了!
以下是内容摘要,已简化并保持在240字符以内: 嗨,我是小米!今天聊聊进程、线程和协程: - **进程**:资源分配基本单位,独立且隔离。 - **线程**:进程内执行单元,轻量级且共享资源。 - **协程**:比线程更轻量,适合I/O密集型任务。 每种都有独特特点和适用场景,选择合适可优化性能。希望对你有所帮助!更多内容,请关注我的公众号“软件求生”。
60 1
|
1月前
|
算法 Java
JDK版本特性问题之想控制 G1 垃圾回收器的并行工作线程数量,如何解决
JDK版本特性问题之想控制 G1 垃圾回收器的并行工作线程数量,如何解决
|
2月前
|
消息中间件 算法 Java
(十四)深入并发之线程、进程、纤程、协程、管程与死锁、活锁、锁饥饿详解
本文深入探讨了并发编程的关键概念和技术挑战。首先介绍了进程、线程、纤程、协程、管程等概念,强调了这些概念是如何随多核时代的到来而演变的,以满足高性能计算的需求。随后,文章详细解释了死锁、活锁与锁饥饿等问题,通过生动的例子帮助理解这些现象,并提供了预防和解决这些问题的方法。最后,通过一个具体的死锁示例代码展示了如何在实践中遇到并发问题,并提供了几种常用的工具和技术来诊断和解决这些问题。本文旨在为并发编程的实践者提供一个全面的理解框架,帮助他们在开发过程中更好地处理并发问题。