带你看懂 JDK21 新特性:虚拟线程(上)

joytrian
发布于 2023-10-19 11:41
浏览
0收藏

免责声明~

任何文章不要过度深思!

万事万物都经不起审视,因为世上没有同样的成长环境,也没有同样的认知水平,更「没有适用于所有人的解决方案」

不要急着评判文章列出的观点,只需代入其中,适度审视一番自己即可,能「跳脱出来从外人的角度看看现在的自己处在什么样的阶段」才不为俗人

怎么想、怎么做,全在乎自己「不断实践中寻找适合自己的大道」

1 全新并发编程模式

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

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

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

2 概述

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

带你看懂 JDK21 新特性:虚拟线程(上)-鸿蒙开发者社区

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

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平台及其工具的多线程设计保持和谐一致。

写在最后

​公众号​​​:​​JavaEdge​​​ 专注分享软件开发全生态相关​​技术文章​​​、​​视频教程​​​资源、热点资讯等,如果喜欢我的分享,给 🐟🐟 点一个​​赞​​​ 👍 或者 ➕​​关注​​ 都是对我最大的支持。


文章转载自公众号: JavaEdge

分类
标签
已于2023-10-19 11:41:36修改
收藏
回复
举报
回复
    相关推荐