Java虚拟线程详解
引言依稀还记得2016年开始学Java的场景时光的距离是如此简短十年时间仿佛隔桌而坐。刚学java时用的还是jdk1.6jdk6到现在最新的jdk版本已经是25了在java圈子里有一个诙谐的说法来形容jdk的快速迭代“新版任你发我用java8你升随你升我用java8”这既是玩笑也是真实写照。就我个人而言使用的最多的也是jdk8在22年后新项目都升级到了jdk17今年的一个项目又开始使用jdk21而jdk21的一个重要特性便是正式引入了虚拟线程Virtual Thread。为什么要引入虚拟线程在jdk21之前Java 的线程模型平台线程 Platform Thread是一对一映射到操作系统线程OS Thread的。这带来了几个根本性问题OS 线程的代价很昂贵每个 OS 线程默认栈大小约 512KB~1MB线程创建/销毁涉及系统调用开销大上下文切换context switch由 OS 调度成本高一台普通服务器能稳定支撑的 OS 线程数通常只有几千个这也是网关、RPC、HTTP服务这类IO密集型服务的核心性能瓶颈。在jdk21中如何创建线程//创建平台线程 Thread.ofPlatform().start(() - System.out.println(platform thread)); //传统方式 - 创建平台线程 Thread t1 new Thread(() - System.out.println(platform thread)); t1.start(); //创建虚拟线程 Thread vt Thread.ofVirtual().start(() - System.out.println(virtual thread));虚拟线程的原理结合Linux中的内核线程Kernel Thread和用户线程User Thread的关系更容易理解虚拟线程的原理。Linux 中线程分两个层面内核线程Kernel Thread 由 OS 内核管理真正在 CPU 上执行是 OS 调度的基本单位。用户线程User Thread 在用户空间实现内核不感知由用户空间的线程库管理调度。两者的映射关系历史上有三种模型1:1 模型一个用户线程对应一个内核线程Linux NPTL现代Linux的默认实现N:1 模型所有用户线程映射到一个内核线程内核完全不感知多线程M:N 模型M个用户线程映射到N个内核线程两级调度jdk21虚拟线程与平台线程的关系与 M:N 模型 类似核心思想都是用少量的“重”线程驱动大量的“轻”线程调度器把对应的内核线程/载体线程切换去执行其他任务避免资源浪费。阻塞时的处理思路也相同用户线程/虚拟线程阻塞时调度器把对应的内核线程/载体线程切换去执行其他任务避免资源浪费。但是两者又有着重要区别1、内核感知程度不同Linux 用户线程N:1或M:N内核完全不感知用户线程的存在 内核只看到内核线程不知道上面跑了多少用户线程 Java 虚拟线程载体线程是真实的 OS 线程内核完全感知载体线程 虚拟线程本身确实对内核不可见但载体线程对内核完全可见2、阻塞处理机制不同这是最关键的区别Linux 用户线程阻塞时的问题 用户线程发起系统调用如 read()→ 内核线程被阻塞 → 整个内核线程卡住 → 该内核线程上的所有用户线程都无法执行 → 这是 N:1 模型的致命缺陷 Java 虚拟线程阻塞时 虚拟线程发起 I/O → JVM 拦截改写为异步 I/O → 虚拟线程从载体线程卸载 → 载体线程立刻去执行其他虚拟线程 → I/O 完成后虚拟线程重新挂载Java 虚拟线程能解决阻塞问题根本原因是 JVM 在底层把阻塞式 I/O 替换成了异步 I/O基于 Linux 的 io_uring 或 epoll这是 Linux 原始用户线程库做不到的事情。3、历史背景不同Linux 的 M:N 模型如早期的 NGPT最终被放弃了现代 Linux 默认使用 1:1 模型NPTL原因是 M:N 实现复杂、调试困难、性能不稳定。Java 虚拟线程建立在现代 OS 已经非常成熟的异步 I/O 基础上站在了更高的起点规避了当年 M:N 模型的主要问题。4、调度器位置不同Linux M:N调度器在用户空间的线程库中如 libpthread Java 虚拟线程调度器在 JVM 内部ForkJoinPool且 JVM 可以感知 所有的 I/O 操作在恰当时机主动触发卸载java 虚拟线程与 Linux M:N 用户线程是同一思想的不同实现。核心区别在于Java 虚拟线程依托 JVM 对 I/O 的全面拦截和异步化改造真正解决了阻塞穿透问题而这正是当初 Linux M:N 模型最终失败的根本原因。用下图可以总结Linux 历史模型 N:1 用户线程A ──┐ 用户线程B ──┼──→ 内核线程1 ← 一个阻塞全部卡死 用户线程C ──┘ M:N 用户线程A ──→ 内核线程1 用户线程B ──→ 内核线程2 ← 理想但实现复杂已被放弃 用户线程C ──┘ 1:1 用户线程A ──→ 内核线程A ← 现代Linux默认简单但线程数受限 用户线程B ──→ 内核线程B Java 虚拟线程本质是改良的M:N 虚拟线程A ──→ 载体线程1OS线程 虚拟线程B ──┘ ↑ 阻塞时自动卸载靠JVM拦截I/O实现 虚拟线程C ──→ 载体线程2OS线程 虚拟线程D ──┘虚拟线程背后的载体线程是什么虚拟线程的载体线程其实也就是平台线程Platform Thread平台线程对应真实的 OS 线程。使用如下代码测试private static Thread getCarrierThread(Thread virtualThread) throws Exception { // Java 21 中 VirtualThread 内部有 carrierThread 字段 Class? vtClass Class.forName(java.lang.VirtualThread); Field carrierField vtClass.getDeclaredField(carrierThread); carrierField.setAccessible(true); return (Thread) carrierField.get(virtualThread); } private static void createVirtualThread() { Thread vt Thread.ofVirtual().start(() - { try { Thread carrier getCarrierThread(Thread.currentThread()); System.out.println(虚拟线程: Thread.currentThread()); System.out.println(载体线程: carrier); } catch (Exception e) { e.printStackTrace(); } });通过main方法启动执行createVirtualThreadvm参数中增加--add-opens java.base/java.langALL-UNNAMED控制台输出日志如下:虚拟线程: VirtualThread[#21]/runnableForkJoinPool-1-worker-1 载体线程: Thread[#22,ForkJoinPool-1-worker-1,5,CarrierThreads]可以看出载体线程实际上是ForkJoinPool-1-worker-线程池中的线程1疯狂创建100个虚拟线程并打印:for (int i 0; i 100; i) { int finalI i; Thread vt2 Thread.ofVirtual().start(() - { try { Thread carrier getCarrierThread(Thread.currentThread()); System.out.println(虚拟线程 finalI : Thread.currentThread()); System.out.println(载体线程 finalI : carrier); } catch (Exception e) { e.printStackTrace(); } }); }输出结果如下:虚拟线程 0 : VirtualThread[#27]/runnableForkJoinPool-1-worker-2 载体线程 0 : Thread[#24,ForkJoinPool-1-worker-2,5,CarrierThreads] 虚拟线程 2 : VirtualThread[#29]/runnableForkJoinPool-1-worker-3 载体线程 2 : Thread[#26,ForkJoinPool-1-worker-3,5,CarrierThreads] 虚拟线程 6 : VirtualThread[#33]/runnableForkJoinPool-1-worker-3 虚拟线程 7 : VirtualThread[#34]/runnableForkJoinPool-1-worker-3 载体线程 7 : Thread[#26,ForkJoinPool-1-worker-3,5,CarrierThreads] 虚拟线程 8 : VirtualThread[#35]/runnableForkJoinPool-1-worker-3 载体线程 8 : Thread[#26,ForkJoinPool-1-worker-3,5,CarrierThreads] 虚拟线程 9 : VirtualThread[#36]/runnableForkJoinPool-1-worker-3 载体线程 9 : Thread[#26,ForkJoinPool-1-worker-3,5,CarrierThreads] 虚拟线程 10 : VirtualThread[#37]/runnableForkJoinPool-1-worker-3 载体线程 10 : Thread[#26,ForkJoinPool-1-worker-3,5,CarrierThreads] 虚拟线程 11 : VirtualThread[#38]/runnableForkJoinPool-1-worker-3 载体线程 11 : Thread[#26,ForkJoinPool-1-worker-3,5,CarrierThreads] ...可以确认虚拟线程默认使用的就是ForkJoinPool的一个线程池实例中的创建的线程。对java stream 并行流比较熟悉的同学会意识到这会与stream并行流所使用的线程池有线程复用吗答案是不会实际是两个ForkJoinPool实例。如下图所示stream并行流使用的线程池是ForkJoinPool.commonPool-worker-使用虚拟线程应该注意什么虚拟线程解决的是 I/O 等待造成的线程资源浪费问题让你用简单的同步代码写出高并发程序。什么情况下适合使用虚拟线程核心判断标准任务是 I/O 密集型线程大量时间在等待高并发 Web 服务每个 HTTP 请求一个虚拟线程支撑万级并发数据库访问大量 JDBC 查询等待数据库响应微服务间 HTTP 调用调用下游服务时的网络等待文件 I/O 操作读写磁盘的等待时间消息队列消费等待消息的长轮询需要从异步代码迁回同步代码将 reactive 代码简化重写简单说线程的大部分时间在 wait / 阻塞而不是在跑 CPU就适合用虚拟线程。什么情况下不适合使用虚拟线程1、CPU 密集型任务如图像处理、加密计算、机器学习推理、大量数学计算。虚拟线程不会让 CPU 跑得更快瓶颈是 CPU 核心数不是线程数。这种场景用 ForkJoinPool 或固定大小线程池反而更合适。2、线程数量本来就很少的场景如果你的应用本来只需要几十个线程传统线程完全够用引入虚拟线程没有收益。3、存在 synchronized 持有锁时发生阻塞Pinning 问题// ❌ 危险synchronized 块内有 I/O 阻塞 → pinning synchronized (lock) { result jdbcConnection.query(...); // 虚拟线程被 pin 住 } // ✅ 改用 ReentrantLock private final ReentrantLock lock new ReentrantLock(); lock.lock(); try { result jdbcConnection.query(...); // 虚拟线程可以正常卸载 } finally { lock.unlock(); }这是虚拟线程目前最重要的陷阱。当虚拟线程在 synchronized 块内部发生阻塞它无法从 carrier thread 卸载称为 pinningjdk24中已经解决退化成平台线程的行为甚至可能造成 carrier thread 耗尽。文章转载自DeepSky丶原文链接https://www.cnblogs.com/deep-sky/p/19656303体验地址http://www.jnpfsoft.com/?from001YH

相关新闻

信息化系统运维管理办法和制度文件

信息化系统运维管理办法和制度文件

一、运维类资料二、信息安全数据安全资料三、等级保护方案,信创资料集软件开发全方位管理资料包清单概览: 任务部署指令书,可行性研究报告全集,项目启动审批文件,产品需求规格详尽说明书,需求调研策略规划&…

2026/7/7 22:27:41 阅读更多 →
lua 二分查找算法实现。

lua 二分查找算法实现。

-- 二分查找(迭代版本,下划线命名) -- param arr 已排序的升序数组 -- param target 要查找的目标值 -- return 找到返回索引(从1开始),否则返回nil function binary_search(arr, target)local left 1loca…

2026/7/6 15:50:21 阅读更多 →
Lua二分查找算法实现及与标量函数对比。

Lua二分查找算法实现及与标量函数对比。

测试目标存在时的测试 数组大小 线性平均时间(s) 二分平均时间(s) 速度比(线性/二分) 10 0.018674 0.019905 0.94 20 0.029383 0.027400 1.07 50 0.060307 0.032418 1.86 100 0.110654 0.039088 2.83 200 0.210350 0.044579 4.72 500 0.517596 0.051270 10.…

2026/7/6 8:31:10 阅读更多 →

最新新闻

Docker工程化:从安装命令到CI/CD交付的系统认知

Docker工程化:从安装命令到CI/CD交付的系统认知

1. 工程化不是加个Dockerfile就完事:从“能跑”到“可交付”的认知断层很多人第一次接触Docker,是在某篇教程里照着敲下docker run hello-world,看到终端输出一行绿色文字,就以为自己“会Docker”了。接着兴冲冲去改项目&#xff…

2026/7/8 15:19:40 阅读更多 →
【Bug已解决】API Key invalid after rotation / Old key cached — Claude Code API Key 轮换后失效解决方案

【Bug已解决】API Key invalid after rotation / Old key cached — Claude Code API Key 轮换后失效解决方案

【Bug已解决】API Key invalid after rotation / Old key cached — Claude Code API Key 轮换后失效解决方案 1. 问题描述 在 Anthropic Console 中轮换(rotate)API Key 后,Claude Code 仍然使用旧 Key,报认证错误: $…

2026/7/8 15:19:40 阅读更多 →
memwatch架构解析:轻量级设计背后的内存检测原理

memwatch架构解析:轻量级设计背后的内存检测原理

memwatch架构解析:轻量级设计背后的内存检测原理 【免费下载链接】memwatch a lightweight debugging tool that can tolerant memory leak and corruption detection. 项目地址: https://gitcode.com/openeuler/memwatch 前往项目官网免费下载:h…

2026/7/8 15:19:40 阅读更多 →
内核日志永不丢失:openeuler/lep Kbox黑匣子分区管理与数据恢复技巧

内核日志永不丢失:openeuler/lep Kbox黑匣子分区管理与数据恢复技巧

内核日志永不丢失:openeuler/lep Kbox黑匣子分区管理与数据恢复技巧 【免费下载链接】lep Linux Kernel Enhancement Patch 项目地址: https://gitcode.com/openeuler/lep 前往项目官网免费下载:https://ar.openeuler.org/ar/ 在Linux系统运行过…

2026/7/8 15:17:32 阅读更多 →
Intel-gcc深度解析:揭秘Intel专属增强版GCC编译器的核心优势

Intel-gcc深度解析:揭秘Intel专属增强版GCC编译器的核心优势

Intel-gcc深度解析:揭秘Intel专属增强版GCC编译器的核心优势 【免费下载链接】Intel-gcc gcc with Intel specific enhancements 项目地址: https://gitcode.com/openeuler/Intel-gcc 前往项目官网免费下载:https://ar.openeuler.org/ar/ Intel-…

2026/7/8 15:17:32 阅读更多 →
hpc-prrte性能优化指南:让并行计算任务运行更高效

hpc-prrte性能优化指南:让并行计算任务运行更高效

hpc-prrte性能优化指南:让并行计算任务运行更高效 【免费下载链接】hpc-prrte hpc-prrte is the process manager of openmpi. It starts, stops, and manages parrallel computing tasks. 项目地址: https://gitcode.com/openeuler/hpc-prrte 前往项目官网免…

2026/7/8 15:17:32 阅读更多 →

日新闻

3大核心能力重塑《明日方舟》游戏体验:MAA自动化助手的革命性突破

3大核心能力重塑《明日方舟》游戏体验:MAA自动化助手的革命性突破

3大核心能力重塑《明日方舟》游戏体验:MAA自动化助手的革命性突破 【免费下载链接】MaaAssistantArknights 《明日方舟》小助手,全日常一键长草!| A one-click tool for the daily tasks of Arknights, supporting all clients. 项目地址: …

2026/7/8 0:00:48 阅读更多 →
MyBatis 批量操作深度优化——从 N+1 到批处理的全路径

MyBatis 批量操作深度优化——从 N+1 到批处理的全路径

MyBatis 批量操作深度优化——从 N1 到批处理的全路径 一、从"功能正确"到"性能可接受"——MyBatis 批量操作的三段式进化 MyBatis 在日常增删改查场景中几乎是无感的——实体映射直观、SQL 控制灵活。但当数据量从千级上升到十万级、百万级,许…

2026/7/8 0:00:48 阅读更多 →
工业负载控制方案:TPD2015FN与PIC18F45K22应用解析

工业负载控制方案:TPD2015FN与PIC18F45K22应用解析

1. 工业负载控制方案概述在工业自动化、电机驱动和照明控制等高需求场景中,可靠地控制电感和电阻负载是核心挑战之一。TPD2015FN作为东芝的8通道高端智能功率开关IC,配合PIC18F45K22微控制器,能够构建一套稳定、高效的负载控制系统。这套组合…

2026/7/8 0:02:48 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/7 14:24:45 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/7 12:34:47 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/7 15:59:06 阅读更多 →

月新闻