Java 19 虚拟线程

前言

在 Java 中,虚拟线程 (JEP-425) 是 JVM 管理的轻量级线程,它有助于我们编写高吞吐量并发应用程序。

1. Java线程模型和虚线程

1.1 平台线程

在 Java 中,经典线程是 java.lang.Thread 类的实例。后面我们也将它们称为平台线程。

传统上,Java 将平台线程视为围绕操作系统 (OS) 线程的瘦包装器。创建这样的平台线程一直很昂贵(由于操作系统维护的堆栈和其他资源很大),因此 Java 一直使用线程池来避免线程创建的开销。

平台线程的数量也必须受到限制,因为这些非常消耗资源的线程会影响整个机器的性能,这主要是因为平台线程被 1:1 映射到 OS 线程。通常,在CPU,网络连接等成为系统瓶颈前,相当数量的平台线程会首先成为系统的瓶颈。

换句话说,在硬件资源稍有冗余的前提下,平台线程首先成为了系统吞吐量的瓶颈。

1.2 平台线程的可扩展性问题

平台线程一直很容易建模、编程和调试,因为它们使用平台的并发单元来表示应用程序的并发单元。它被称为一个线程一个请求的模式。

但是这种模式限制了服务器的吞吐量,因为并发请求的数量(服务器可以处理)与服务器的硬件性能成正比。因此,即使在多核处理器中,可用线程的数量也必须受到限制。

除了线程数量之外,延迟也是一个大问题。如果我们仔细观察,在当今的微服务世界中,请求是通过在多个系统和服务器上获取/更新数据来服务的。在应用程序等待来自其他服务器的信息时,当前平台线程保持在空闲状态。这是对计算资源的浪费,也是实现高吞吐量应用程序的主要障碍。

1.3 反应式编程 (Reactive Programming) 的问题

反应式编程解决了平台线程等待其他系统响应的问题。异步 API 不等待响应,而是通过回调工作。每当线程调用异步 API 时,平台线程都会返回到池中,直到响应从远程系统或数据库返回。稍后,当响应到达时,JVM 将从池中分配另一个线程来处理响应,依此类推。这样,多个线程参与处理单个异步请求。

在异步编程中,延迟被消除了,但由于硬件限制,平台线程的数量仍然有限,因此我们对可扩展性有限制。另一个大问题是这样的异步程序在不同的线程中执行,因此很难调试或分析它们。

此外,我们必须采用一种新的编程风格,远离典型的循环和条件语句。新的 lambda 样式语法使得理解现有代码和编写程序变得困难,因为我们现在必须将程序分解为多个可以独立和异步运行的代码块。

所以我们可以说,虚拟线程还通过适应传统语法来提高代码质量,同时具有反应式编程的好处。

1.4 虚拟线程的前景

与传统线程类似,虚拟线程也是 java.lang.Thread 的一个实例,它在底层 OS 线程上运行其代码,但它不会在代码的整个生命周期内阻塞 OS 线程。保持操作系统线程空闲意味着许多虚拟线程可以在同一个操作系统线程上运行它们的 Java 代码,从而有效地共享它。

值得一提的是,我们可以在一个应用程序中创建非常多的虚拟线程(数百万),而不依赖于平台线程的数量。这些虚拟线程由 JVM 管理,因此它们也不会增加额外的上下文切换开销,因为它们作为普通 Java 对象存储在 RAM 中。

与传统线程类似,应用程序的代码在请求的整个持续时间内都在虚拟线程中运行(以每个请求线程的方式),但虚拟线程仅在 CPU 上执行计算时才消耗操作系统线程。它们在等待或休眠时不会阻塞 OS 线程。

虚拟线程有助于实现与具有相同硬件配置的异步 API 相同的高可扩展性和吞吐量,而不会增加语法复杂性。

虚拟线程最适合执行大部分时间都处于阻塞状态的代码,例如等待数据到达网络套接字或等待队列中的元素。

2. 平台线程和虚拟线程的区别

虚拟线程始终是守护线程。 Thread.setDaemon(false) 方法不能将虚拟线程更改为非守护线程。请注意,当所有启动的非守护线程都终止时,JVM 终止。这意味着 JVM 在退出之前不会等待虚拟线程完成。

Thread virtualThread = ...; //创建虚拟线程
//virtualThread.setDaemon(true);  //没有作用

虚拟线程始终具有正常优先级,并且即使使用 setPriority(n) 方法,也无法更改优先级。在虚拟线程上使用此方法无效。

Thread virtualThread = ...; //创建虚拟线程
//virtualThread.setPriority(Thread.MAX_PRIORITY);  //没有作用
  • 虚拟线程不是线程组的活动成员。在虚拟线程上调用时,Thread.getThreadGroup() 返回一个名为 VirtualThreads 的占位符线程组。
  • 虚拟线程不支持 stop()suspend()resume() 方法。这些方法在虚拟线程上调用时会引发 UnsupportedOperationException

3. 比较平台线程和虚拟线程的性能

让我们了解两种线程在执行相同的代码时的区别。

我们有一个非常简单的任务,在控制台中打印一条消息之前等待 1 秒。

final AtomicInteger atomicInteger = new AtomicInteger();

Runnable runnable = () -> {
   
  try {
   
    Thread.sleep(Duration.ofSeconds(1));
  } catch(Exception e) {
   
      System.out.println(e);
  
  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值