当前位置: 首页 > news >正文

Java21 虚拟线程

虚拟线程(Virtual Thread)

虚拟线程是JDK 21中最受关注且对并发编程影响深远的特性。虚拟线程极大地降低了创建和管理线程的成本,允许开发人员轻松地编写和维护高并发应用程序。由于其轻量级特性,开发者可以创建大量的并发任务,而不会像传统线程那样面临线程上下文切换和内存消耗等问题。虚拟线程与Go语言中的协程类似,使得编写高并发、高吞吐量的应用程序变得更加简单和高效,尤其在处理I/O密集型任务时,能够显著提升系统的并发能力。

传统线程和虚拟线程

创建操作系统线程(传统线程):

Thread thread = new Thread(() -> {System.out.println("Hello, World!");
});
  • 调度层次:此代码创建了一个标准的Java平台线程(即操作系统线程),它是由Java语言提供的java.lang.Thread类实例化并启动的。这类线程是操作系统内核直接调度和管理的实体,与操作系统的原生线程一一对应。它们的生命周期、调度和上下文切换由底层操作系统负责。
  • 资源消耗:操作系统线程需要占用较多资源(内存、CPU等),而且每次创建和销毁线程都比较费时费力。如果创建大量线程,会增加系统负担。
  • 并发能力:操作系统线程可以并发执行任务,但当线程数很多时,会导致操作系统的限制和频繁的上下文切换,反而降低性能。
  • 阻塞行为:如果线程因等待I/O等操作而阻塞,它会释放CPU给其他线程使用,但仍占用系统资源。大量阻塞线程会浪费资源,降低效率。
  • API与语义:可以通过 new Thread() 来创建线程,并用 start() 方法启动它。可以用Lambda表达式传递任务给线程。

创建Java虚拟线程:

Thread virtualThread = Thread.startVirtualThread(() -> {System.out.println("Hello, World!");
});
  • 调度层次:这段代码创建了一个Java虚拟线程,它是Java平台在JDK 19发布的,虚拟线程不直接对应操作系统线程,JVM通过少量的平台线程来调度大量的虚拟线程,避免了传统线程的开销。
  • 资源消耗:创建和销毁虚拟线程的成本非常低,几乎不消耗内存,适合处理成千上万的并发任务。
  • 并发能力:由于虚拟线程的轻量化特性,它们能提供更高的并发能力,尤其在处理大量I/O阻塞任务时非常高效。
  • 阻塞行为:虚拟线程在阻塞时不会占用平台线程,JVM会将它们从平台线程解绑,避免浪费资源,并在需要时重新绑定到可用平台线程。
  • API与语义:使用静态方法Thread.startVirtualThread()直接创建并启动一个虚拟线程,同样传入一个Lambda表达式作为线程的Runnable任务。注意,这是针对Project Loom新增的API,反映了虚拟线程特有的创建和启动方式。

有了虚拟线程,线程池还有必要存在吗?

创建线程 就像是 招人,而 线程池 是一群 已经招进来的工人。
多线程 是 多个工人 在做不同的工作,线程池 则让 一个工人 可以在不同任务之间 灵活切换。
密集I/O 是工人空闲时间多,密集计算 是工人需要长时间集中工作。
虚拟线程 则能够让工人在任务空闲时,快速切换到其他任务,提高效率,特别适合 I/O密集型 操作。

什么情况需要保留线程池呢?

虚拟线程的引入确实极大的降低创建和管理线程的开销,使用并发编程更高效便捷,但并不意味着多线程或线程弛就变得多余。
1、任务管理:线程池提供了任务队列,线程复用,拒绝策略等高级功能,这些特性对应管理任务类型线程(包括虚拟线程)的工作负载都是至关重要的。列如:可以通过线程池设计最大并发数,超时处理,异常处理等
2、声明周期控制:线程池允许开发者更精细的控制线程的生命周期,在完成一系列任务后可以优雅的关闭和资源回收。即便虚拟线程简化创建和销毁,但某些场景仍需要有组织地结束并清理资源。
3、兼容性和迁移成本:目前大量java库和架构都是基于线程池设计和实现,直接切换成虚拟线程的代码修改和测试成本太大。

如何使用虚拟线程?

从 Java 19 开始,虚拟线程作为预览特性引入,并在 Java 21 中成为正式特性。可以通过 java.util.concurrent 包下的 ExecutorService 来创建和管理虚拟线程。

1. 启动虚拟线程

可以使用 Thread.ofVirtual().start() 来创建一个虚拟线程并启动它。代码示例如下:

public class VirtualThreadExample {public static void main(String[] args) {// 创建并启动一个虚拟线程Thread virtualThread = Thread.ofVirtual().start(() -> {System.out.println("Hello 虚拟线程!");});// 等待虚拟线程执行完毕try {virtualThread.join();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}
2. 使用虚拟线程池

通常情况下,我们不直接管理虚拟线程,而是使用线程池来统一管理线程。Java 提供了新的虚拟线程池支持,可以通过 Executors.newVirtualThreadPerTaskExecutor() 来创建一个虚拟线程池。这样,每个任务都会在一个独立的虚拟线程中执行。

import java.util.concurrent.*;public class VirtualThreadPoolExample {public static void main(String[] args) {// 创建一个虚拟线程池ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();// 提交任务到线程池for (int i = 0; i < 5; i++) {final int taskId = i;executorService.submit(() -> {System.out.println("任务" + taskId + "===虚拟线程正在执行");});}// 关闭线程池executorService.shutdown();}
}
3. 使用 StructuredTaskScope

StructuredTaskScope 是 Java 21 中引入的用于简化并发控制的一个 API,它可以用于管理一组任务并确保它们在正确的时间完成。

import java.util.concurrent.*;public class VirtualThreadScopeExample {public static void main(String[] args) {try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {// 启动并发任务scope.fork(() -> {System.out.println("虚拟线程正在执行任务一");});scope.fork(() -> {System.out.println("虚拟线程正在执行任务二");});// 等待所有任务完成scope.join();} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}

区别:

并发性能对比

假设我们要测试在高并发场景下,虚拟线程和传统线程的性能差异。下面的示例演示了如何在两种不同的线程池中并行执行大量任务。

传统线程池并发性能
import java.util.concurrent.*;public class TraditionalThreadPoolPerformance {public static void main(String[] args) throws InterruptedException {long startTime = System.nanoTime();// 创建一个固定大小的传统线程池ExecutorService executorService = Executors.newFixedThreadPool(4);for (int i = 0; i < 1_000_000; i++) {executorService.submit(() -> {// 模拟任务try {Thread.sleep(1);} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}executorService.shutdown();executorService.awaitTermination(1, TimeUnit.MINUTES);long endTime = System.nanoTime();System.out.println("传统线程执行时间:" + (endTime - startTime) / 1_000_000 + " ms");}
}
  • 这里创建了一个固定大小为 4 的传统线程池,提交 100 万个任务。由于线程池大小固定,任务会排队等待空闲线程,导致性能瓶颈。
虚拟线程池并发性能
import java.util.concurrent.*;public class VirtualThreadPoolPerformance {public static void main(String[] args) throws InterruptedException {long startTime = System.nanoTime();// 创建一个虚拟线程池ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();for (int i = 0; i < 1_000_000; i++) {executorService.submit(() -> {// 模拟任务try {Thread.sleep(1);} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}executorService.shutdown();executorService.awaitTermination(1, TimeUnit.MINUTES);long endTime = System.nanoTime();System.out.println("虚拟线程执行时间:" + (endTime - startTime) / 1_000_000 + " ms");}
}
  • 使用虚拟线程池,虚拟线程池不会有传统线程池中的线程数限制,所有任务都会立即分配一个虚拟线程执行,因此能够更高效地处理大量并发任务。

使用虚拟线程的场景

虚拟线程非常适合以下场景:

  1. 高并发的 I/O 密集型任务:虚拟线程特别适合处理大量并发的 I/O 操作,例如网络请求、数据库操作等,因为它们的创建和管理开销小,可以支持更多的并发任务。
  2. 简单的任务并发:对于简单的任务,并发处理不需要复杂的线程池和任务调度,虚拟线程可以提供更简洁的编程模型。
  3. 减少上下文切换的开销:与传统线程相比,虚拟线程的上下文切换开销较低,可以提高系统的吞吐量和响应性。

http://www.mrgr.cn/news/80270.html

相关文章:

  • 2024告别培训班 数通、安全、云计算、云服务、存储、软考等1000G资源分享
  • B4X编程语言:B4X控件方法汇总
  • LabVIEW汽车综合参数测量
  • 专业140+总分400+北京理工大学826信号处理导论考研经验北理工电子信息与通信工程,真题,大纲,参考书。
  • 又细又长的马尾:tail
  • hutool一些典型的方法使用笔记
  • epoll反应堆模型
  • python导出requirements.txt的方法
  • 浅议Flink lib包下的依赖项
  • Mybatisplus教学
  • 基线检查:Windows安全基线.【手动 || 自动】
  • SQL server学习05-查询数据表中的数据(上)
  • gorm源码解析(二):核心设计与初始化
  • 计算机网络知识点全梳理(二.HTTP知识点总结)
  • PostgreSQL JSON/JSONB 查询与操作指南
  • git使用教程(超详细)-透彻理解git
  • 计算机网络知识点全梳理(一.TCP/IP网络模型)
  • Python自动化操作文档系列
  • 【Flink-scala】DataStream编程模型之延迟数据处理
  • vscode+msys2+clang+xmake c++开发环境搭建
  • 2024CAT开发测试 Web应用赛道
  • Nginx配置示例教程
  • ARM64平台实时linux操作系统xenomai4(EVL)构建安装简述
  • 使用rust语言创建python模块(pyo3+maturin)
  • 高等动力学中的正则变换
  • 在 macOS 下安装和使用 Clang Static Analyzer