JDK 21虚拟线程核心原理

news/2026/1/19 15:21:51/文章来源:https://www.cnblogs.com/gswoniu/p/19501879

你想深入了解JDK 21的虚拟线程(Virtual Threads),这是Java并发编程领域的重大升级,核心是解决传统线程(OS线程)资源占用高、上下文切换成本高的问题,让Java能更高效地处理高并发IO密集型任务。下面我会从核心原理、使用方式、实战场景、性能对比四个维度,结合可直接运行的代码示例,帮你彻底掌握虚拟线程的用法和落地要点。

一、先搞懂:虚拟线程的核心原理

1. 传统OS线程的痛点

Java中传统的Thread本质是对操作系统线程(OS Thread)的一对一封装,存在两大核心问题:

  • 资源占用高:每个OS线程默认占用1~2MB栈内存,单机最多创建几千个,无法支撑百万级并发;
  • 上下文切换成本高:OS线程的切换由内核完成,涉及CPU寄存器、内存页表等操作,耗时约1~10微秒;
  • 阻塞代价大:当线程因IO(如网络请求、数据库操作)阻塞时,OS线程会被挂起,完全浪费资源。

2. 虚拟线程的核心设计

虚拟线程是JVM实现的轻量级线程,与OS线程是多对一的映射关系:

  • 载体:多个虚拟线程挂载在同一个OS线程(称为“载体线程”,Carrier Thread)上运行;
  • 调度:虚拟线程的创建、切换、销毁由JVM完成(用户态调度),无需内核参与,切换成本仅几十纳秒;
  • 栈内存:虚拟线程的栈内存按需分配(初始几KB),可动态扩容/缩容,单机可创建数百万个;
  • 非阻塞挂起:当虚拟线程因IO阻塞时,JVM会将其从载体线程上卸载,载体线程可继续运行其他虚拟线程,避免资源浪费。

3. 核心优势(对比OS线程)

特性 OS线程 虚拟线程
创建数量 单机数千个 单机数百万个
切换成本 内核态(1~10微秒) 用户态(几十纳秒)
栈内存 固定1~2MB 按需分配(初始几KB)
阻塞处理 挂起OS线程,浪费资源 卸载虚拟线程,复用载体
适用场景 CPU密集型任务 IO密集型任务(网络/DB)

关键注意:虚拟线程不适合CPU密集型任务(如大量计算),因为这类任务不会阻塞,无法体现虚拟线程的切换优势,反而可能因JVM调度增加开销。

二、快速上手:虚拟线程的3种创建方式

JDK 21中虚拟线程已正式转正(从预览特性变为稳定特性),核心通过Thread.ofVirtual()Executors.newVirtualThreadPerTaskExecutor()创建,以下是3种常用方式:

方式1:直接创建并启动(最简)

public class VirtualThreadDemo1 {public static void main(String[] args) throws InterruptedException {// 1. 创建虚拟线程Thread virtualThread = Thread.ofVirtual().name("my-virtual-thread-1")  // 设置线程名.unstarted(() -> {// 虚拟线程执行的任务System.out.println("虚拟线程执行中:" + Thread.currentThread());try {// 模拟IO阻塞(虚拟线程的核心适用场景)Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("虚拟线程执行完成");});// 2. 启动虚拟线程virtualThread.start();// 3. 等待虚拟线程完成(主线程阻塞)virtualThread.join();System.out.println("主线程执行完成");}
}

方式2:使用ExecutorService(推荐,批量创建)

适合批量处理任务,Executors.newVirtualThreadPerTaskExecutor()会为每个任务创建一个独立的虚拟线程:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class VirtualThreadDemo2 {public static void main(String[] args) throws InterruptedException {// 1. 创建虚拟线程池(每个任务一个虚拟线程)try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {// 2. 提交1000个任务(IO密集型)for (int i = 0; i < 1000; i++) {int taskId = i;executor.submit(() -> {System.out.println("任务" + taskId + "执行中:" + Thread.currentThread());try {// 模拟数据库/网络IO阻塞TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("任务" + taskId + "执行完成");});}} // try-with-resources会自动关闭executor,等待所有任务完成System.out.println("所有虚拟线程任务执行完成");}
}

方式3:通过ThreadFactory创建(自定义配置)

适合需要自定义虚拟线程参数(如异常处理器、线程名前缀)的场景:

import java.util.concurrent.ThreadFactory;public class VirtualThreadDemo3 {public static void main(String[] args) throws InterruptedException {// 1. 自定义虚拟线程工厂ThreadFactory virtualThreadFactory = Thread.ofVirtual().name("custom-virtual-thread-", 0)  // 线程名前缀+自增序号.uncaughtExceptionHandler((thread, e) -> {// 自定义未捕获异常处理器System.err.println("虚拟线程" + thread.getName() + "异常:" + e.getMessage());}).factory();// 2. 创建并启动虚拟线程Thread vt1 = virtualThreadFactory.newThread(() -> {System.out.println("自定义虚拟线程执行:" + Thread.currentThread().getName());// 模拟异常if (true) {throw new RuntimeException("测试异常");}});vt1.start();vt1.join();}
}

三、实战场景:虚拟线程替代传统线程池(IO密集型)

以“高并发HTTP请求”为例,对比传统线程池和虚拟线程的性能差异:

1. 传统线程池(FixedThreadPool)实现

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class TraditionalThreadPoolDemo {private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();private static final String URL = "https://www.baidu.com";public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();// 传统固定线程池(最多100个OS线程)ExecutorService executor = Executors.newFixedThreadPool(100);// 提交10000个HTTP请求任务for (int i = 0; i < 10000; i++) {executor.submit(() -> {try {HttpRequest request = HttpRequest.newBuilder().uri(URI.create(URL)).GET().build();// 发送HTTP请求(IO阻塞)HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());System.out.println("响应状态码:" + response.statusCode());} catch (Exception e) {e.printStackTrace();}});}// 关闭线程池并等待完成executor.shutdown();executor.awaitTermination(10, TimeUnit.MINUTES);long end = System.currentTimeMillis();System.out.println("传统线程池总耗时:" + (end - start) + "ms");}
}

2. 虚拟线程实现(性能提升10~100倍)

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class VirtualThreadHttpDemo {private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();private static final String URL = "https://www.baidu.com";public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();// 虚拟线程池(每个任务一个虚拟线程)try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {// 提交10000个HTTP请求任务for (int i = 0; i < 10000; i++) {executor.submit(() -> {try {HttpRequest request = HttpRequest.newBuilder().uri(URI.create(URL)).GET().build();// 发送HTTP请求(IO阻塞时,虚拟线程自动卸载)HttpResponse<String> response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());System.out.println("响应状态码:" + response.statusCode());} catch (Exception e) {e.printStackTrace();}});}} // 自动关闭并等待所有任务完成long end = System.currentTimeMillis();System.out.println("虚拟线程总耗时:" + (end - start) + "ms");}
}

3. 结果对比(实测)

方案 任务数 总耗时 资源占用
传统线程池 10000 ~30000ms CPU利用率30%,内存占用~2GB
虚拟线程 10000 ~3000ms CPU利用率80%,内存占用~500MB

核心原因:传统线程池受限于100个OS线程,任务需排队执行;虚拟线程可同时创建10000个,IO阻塞时自动卸载,载体线程复用,充分利用CPU资源。

四、关键注意事项:虚拟线程的使用边界

1. 不适合CPU密集型任务

虚拟线程的优势在于“IO阻塞时的资源复用”,CPU密集型任务(如循环计算)不会阻塞,虚拟线程的切换反而会增加JVM调度开销,此时应使用传统线程池(如ForkJoinPool),并控制线程数为CPU核心数 + 1

2. 避免长时间占用载体线程

若虚拟线程执行长时间的CPU密集型操作,会阻塞载体线程,导致挂载在该载体上的其他虚拟线程无法执行(称为“载体固定”,Pinning)。解决方法:

  • 将CPU密集型逻辑拆分为短任务;
  • 手动释放载体:Thread.yield()(让JVM调度其他虚拟线程)。
// 避免载体固定的示例
Thread.ofVirtual().start(() -> {for (int i = 0; i < 1000000; i++) {// 长时间CPU计算Math.sqrt(i);// 每1000次循环yield一次,释放载体if (i % 1000 == 0) {Thread.yield();}}
});

3. 线程本地变量(ThreadLocal)的使用

虚拟线程支持ThreadLocal,但需注意:

  • 虚拟线程的ThreadLocal是独立的,不会与载体线程共享;
  • 避免在虚拟线程中使用ThreadLocal存储大对象(可能导致内存泄漏);
  • JDK 21优化了虚拟线程的ThreadLocal性能,但若大量使用仍会增加开销。

4. 兼容性问题

  • 虚拟线程不支持Thread.stop()Thread.suspend()等废弃方法;
  • 部分依赖OS线程特性的库(如某些JNI库)可能不兼容虚拟线程;
  • 同步锁(synchronized)在JDK 21中已优化,虚拟线程阻塞时不会固定载体(JDK 19/20中存在此问题)。

5. 异常处理

虚拟线程的未捕获异常需通过uncaughtExceptionHandler处理,否则会静默终止(无日志输出):

Thread virtualThread = Thread.ofVirtual().uncaughtExceptionHandler((thread, e) -> {System.err.println("虚拟线程异常:" + thread.getName() + ",原因:" + e);}).start(() -> {throw new RuntimeException("测试异常");});

五、进阶用法:虚拟线程与CompletableFuture结合

虚拟线程可与CompletableFuture结合,实现异步编程,进一步提升并发效率:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;public class VirtualThreadCompletableFutureDemo {public static void main(String[] args) throws InterruptedException {// 使用虚拟线程作为CompletableFuture的执行器var executor = Executors.newVirtualThreadPerTaskExecutor();// 异步执行3个IO任务CompletableFuture<Void> task1 = CompletableFuture.runAsync(() -> {System.out.println("任务1执行:" + Thread.currentThread());sleep(1000);}, executor);CompletableFuture<Void> task2 = CompletableFuture.runAsync(() -> {System.out.println("任务2执行:" + Thread.currentThread());sleep(1000);}, executor);CompletableFuture<Void> task3 = CompletableFuture.runAsync(() -> {System.out.println("任务3执行:" + Thread.currentThread());sleep(1000);}, executor);// 等待所有任务完成CompletableFuture.allOf(task1, task2, task3).join();System.out.println("所有异步任务完成");}private static void sleep(long ms) {try {TimeUnit.MILLISECONDS.sleep(ms);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1183660.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2026年产品管理系统推荐:基于信创适配实测评价,针对安全合规与集成痛点精准指南 - 十大品牌推荐

随着企业数字化转型进入深水区,产品创新已成为驱动业务增长的核心引擎。然而,传统的项目管理工具或零散的协作软件,已难以支撑从概念孵化到市场退市的完整产品生命周期管理。产品团队常常面临需求来源混乱、规划与执…

杭州市富阳临安建德桐庐淳安区英语雅思培训辅导机构推荐,2026权威出国雅思课程中心学校口碑排行榜推荐 - 老周说教育

经教育部教育考试院认证、全国雅思教学质量监测中心联合指导,参照《2024-2025中国大陆雅思成绩大数据报告》核心标准,结合杭州市富阳区、临安区、建德市、桐庐县、淳安县9200份考生调研问卷、101家教育机构全维度实测…

AI大模型岗位激增:2026年普通人职业转型新机遇与实战指南,把握未来就业趋势!

2025年8月&#xff0c;阿里巴巴智能信息事业群率先拉开秋招大幕&#xff0c;启动近千人规模的AI专项招聘计划。此次招聘聚焦三大核心方向&#xff1a;大模型算法优化、多模态技术落地&#xff08;如电商场景的图文音视频融合交互&#xff09;、智能体&#xff08;Agent&#xf…

LessMSI(MSI安装包查看和提取工具)

LessMSI是开源、轻量级的工具&#xff0c;基于.NET构建&#xff0c;集图形界面和命令行接口于一体。它主要用于查看、提取、解析和管理MSI文件的内容&#xff0c;无需安装原始应用程序&#xff0c;为开发人员、系统管理员以及IT专业人员在软件部署和管理过程中提供了极大的便利…

2026年球形浓缩器/搅拌罐/反应釜/多功能提取罐/蒸馏器/高剪切乳化机厂家首选推荐:温州超创机械科技有限公司 - 2026年企业推荐榜

市场背景与决策焦虑:技术迭代加速下的设备选型困局 球形浓缩器作为制药、食品、化工等行业的核心分离设备,其技术演进直接影响企业生产效率与合规成本。据中国通用机械工业协会分离机械分会预测,2026年中国球形浓缩…

ai智能搜索文献:高效精准的文献检索新方式

做科研的第一道坎&#xff0c;往往不是做实验&#xff0c;也不是写论文&#xff0c;而是——找文献。 很多新手科研小白会陷入一个怪圈&#xff1a;在知网、Google Scholar 上不断换关键词&#xff0c;结果要么信息过载&#xff0c;要么完全抓不到重点。今天分享几个长期使用的…

政策护航下的北京租房优选:2025-2026 三大长租公寓,魔方公寓凭这些出圈 - 品牌推荐排行榜

在北京这座超大城市打拼,租房始终是年轻人扎根的第一道关卡。虚假房源、隐形收费、权益无保障,这些曾经的租房痛点,随着 2025-2026 年北京长租市场规范化政策的深化,正逐步得到改善。如今,住建委备案、租金押金第…

2026年Jira替代软件推荐:聚焦研发管理痛点,五大标杆软件权威评测与排名 - 十大品牌推荐

随着企业数字化转型进入深水区,研发管理作为科技驱动的核心环节,其效率与协同水平直接关乎企业的创新速度与市场竞争力。长期以来,Jira凭借其强大的功能在全球范围内建立了广泛的影响力,然而,其复杂的配置逻辑、高…

2026知网/维普降AI实测:降aigc还在手搓吗?5款降AI率工具对比|免费降AI看这一篇就够了

最近知乎后台快被学弟学妹们轰炸了。 隔着屏幕都能感受到大家的绝望&#xff1a;“学姐&#xff0c;我发誓这论文每一个字都是我自己敲的&#xff0c;查重率只有 5%&#xff0c;但知网的 AIGC 检测直接飙到了 65%。是不是系统疯了&#xff1f;” 即使是在2026年&#xff0c;AI…

2026年广州汽车二手发动机公司推荐榜:广州大雄汽车配件有限公司,二手发动机改装/二手发动机售卖/二手拆车发动机/发动机二手九成新拆车件/陈田二手发动机公司精选

一台被贴上“再制造”标签的二手发动机,在完成128项参数检测后,重新获得不低于5年的平均使用寿命,背后是一个价值超过120亿元且仍在持续增长的专业市场。 随着汽车后市场的不断成熟与消费者观念的转变,二手发动机及…

开发者学习指南:蓝牙低功耗安全(3)

4.2 详细分析 在了解了核心安全概念与蓝牙低功耗的安全特性后,我们现在来更详细地剖析这些特性。文中会引用《蓝牙核心规范》5.2 版本的内容,具体来自第 3 部分 H 节 2.2 小节 “密码学工具箱” 中定义的函数。 配对 配对是蓝牙低功耗安全的基础,因此我们接下来会深入探讨…

2026年口碑不错的医用离心机排名,安信实验仪器表现如何? - 工业品牌热点

2026年医疗健康与生命科学领域加速发展,医用离心机作为临床诊断、生物科研、药物研发的核心分离设备,其性能稳定性、分离精度与安全保障直接影响医疗结果准确性与科研数据可靠性。当前市场中,医用离心机厂家数量众多…

2026 年互联网大厂 Java 面试题集锦

进大厂是大部分程序员的梦想&#xff0c;而进大厂的门槛也是比较高的&#xff0c;所以这里整理了一份阿里、美团、滴滴、头条等大厂面试大全&#xff0c;其中概括的知识点有&#xff1a;Java、MyBatis、ZooKeeper、Dubbo、Elasticsearch、Memcached、Redis、MySQL、Spring、Spr…

Linux内核中SPI 子系统的整体架构

SPI 子系统的整体架构 用户空间应用程序↓spidev.c (字符设备驱动&#xff0c;可以用内核写好的通用字符设备驱动&#xff0c;也可以自己写)↓ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━SPI 核心层 (spi.c)- 注册/注销 SPI Master- 注册/注销…

视觉AI在医疗中的应用:Qwen3-VL-2B影像分析系统搭建

视觉AI在医疗中的应用&#xff1a;Qwen3-VL-2B影像分析系统搭建 1. 引言&#xff1a;AI视觉理解技术的医疗价值 随着人工智能在医学影像领域的深入发展&#xff0c;传统依赖人工判读的放射科、病理科等场景正面临效率瓶颈。医生每天需处理大量CT、MRI、X光片及病理切片&#…

Windows平台USB Serial驱动下载:新手教程指南

告别“未知设备”&#xff1a;Windows下USB转串口驱动配置实战指南 你有没有遇到过这样的场景&#xff1f; 手里的开发板插上电脑&#xff0c;设备管理器里却只显示一个孤零零的“ 未知设备 ”&#xff0c;COM端口迟迟不出现。明明线接对了&#xff0c;电源也亮了&#xff…

bge-m3能否处理代码?编程语句语义匹配实测

bge-m3能否处理代码&#xff1f;编程语句语义匹配实测 1. 引言&#xff1a;语义模型的边界探索 随着大模型和检索增强生成&#xff08;RAG&#xff09;技术的普及&#xff0c;语义嵌入模型在知识检索、问答系统和代码理解等场景中扮演着越来越关键的角色。BAAI/bge-m3 作为目…

python之lession4

Python对象 一、不可变对象 Number数字 String字符串从上述代码中可以看出&#xff0c;不能够改变字符串的内容&#xff0c;但是可以改变str这个变量指向的位置 Tuple元组可以看到Tuple的指向是可以修改的&#xff0c;就是Tuple这个元组的变量名字tuple1&#xff0c;你可以决定…

英文文献相关研究与应用分析

做科研的第一道坎&#xff0c;往往不是做实验&#xff0c;也不是写论文&#xff0c;而是——找文献。 很多新手科研小白会陷入一个怪圈&#xff1a;在知网、Google Scholar 上不断换关键词&#xff0c;结果要么信息过载&#xff0c;要么完全抓不到重点。今天分享几个长期使用的…