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。


目录
相关文章
|
1月前
|
Oracle Java 关系型数据库
一次惨痛的面试:“网易提前批,我被虚拟线程问倒了”
【5月更文挑战第13天】一次惨痛的面试:“网易提前批,我被虚拟线程问倒了”
60 4
|
9天前
|
分布式计算 JavaScript 前端开发
多线程、多进程、协程的概念、区别与联系
多线程、多进程、协程的概念、区别与联系
20 1
|
11天前
|
数据挖掘 调度 开发者
Python并发编程的艺术:掌握线程、进程与协程的同步技巧
并发编程在Python中涵盖线程、进程和协程,用于优化IO操作和响应速度。`threading`模块支持线程,`multiprocessing`处理进程,而`asyncio`则用于协程。线程通过Lock和Condition Objects同步,进程使用Queue和Pipe通信。协程利用异步事件循环避免上下文切换。了解并发模型及同步技术是提升Python应用性能的关键。
37 5
|
8天前
|
监控 程序员 调度
协程实现单线程并发(入门)
协程实现单线程并发(入门)
14 1
|
16天前
|
设计模式 NoSQL Java
网易面试:SpringBoot如何开启虚拟线程?
虚拟线程(Virtual Thread)也称协程或纤程,是一种轻量级的线程实现,与传统的线程以及操作系统级别的线程(也称为平台线程)相比,它的创建开销更小、资源利用率更高,是 Java 并发编程领域的一项重要创新。 > PS:虚拟线程正式发布于 Java 长期支持版(Long Term Suort,LTS)Java 21(也就是 JDK 21)。 虚拟线程是一种在 Java 虚拟机(JVM)层面实现的逻辑线程,不直接和操作系统的物理线程一一对应,因此它可以减少上下文切换所带来的性能开销。 操作系统线程、普通线程(Java 线程)和虚拟线程的关系如下: ![image.png](https:
26 0
网易面试:SpringBoot如何开启虚拟线程?
|
11天前
|
Java 开发者 计算机视觉
探索Python中的并发编程:线程与协程
本文将深入探讨Python中的并发编程,重点比较线程和协程的工作机制、优缺点及其适用场景,帮助开发者在实际项目中做出更明智的选择。
|
14天前
|
存储 Java 调度
Android面试题之Kotlin协程到底是什么?它是线程吗?
本文探讨了协程与线程的区别,指出协程并非线程,而是轻量级的线程替代。协程轻量体现在它们共享调用栈,内存占用少,仅需几个KB。协程切换发生在用户态,避免了昂贵的内核态切换。在Kotlin中,协程通过Continuation对象实现上下文保存,允许高效并发执行,而不会像线程那样消耗大量资源。通过`runBlocking`和`launch`示例展示了协程的非阻塞挂起特性。总结来说,协程的轻量主要源于内存占用少、切换开销低和高并发能力。
18 0
|
15天前
|
存储 网络协议 Java
【JDK21】详解虚拟线程
【JDK21】详解虚拟线程
14 0
|
1月前
|
消息中间件 安全 调度
基于Python的性能优化(线程、协程、进程)
一、多线程 在CPU不密集、IO密集的任务下,多线程可以一定程度的提升运行效率。
|
1天前
|
数据采集 Java Unix
10-多线程、多进程和线程池编程(2)
10-多线程、多进程和线程池编程