跳到正文
Elaine Blog
返回

虚拟线程、固定与观测

并发编程

虚拟线程让“每个任务一个线程”的阻塞式代码承载更高并发,但不会增加 CPU、数据库连接或下游配额。迁移重点是移除平台 线程池造成的线程稀缺,同时保留资源限流、截止时间、取消和可观测性。

Table of contents

Open Table of contents

运行一千个阻塞任务

下载虚拟线程样例下载, 运行:

java -cp target/classes dev.elaine.concurrency.VirtualThreadSample

程序为一千个任务各创建一个虚拟线程,每个任务阻塞 10 毫秒。输出为:

任务数=1000,校验和=499500

虚拟线程在 Java 21 正式发布。它是 Thread,由 JVM 调度到少量 Carrier 平台线程;阻塞在支持的 JDK I/O 或并发原语时, 通常会卸载,让 Carrier 执行其他虚拟线程。

不要池化虚拟线程

平台线程昂贵,所以线程池限制线程数量;虚拟线程便宜,应为每个任务创建。并发限制应该保护实际稀缺资源:

var databasePermits = new java.util.concurrent.Semaphore(50);

即使能创建十万个虚拟线程,数据库仍可能只有 50 个连接。让剩余任务等待 Semaphore、连接池或有界入口,而不是同时压向 下游。

Pinning 必须区分 JDK 版本

在 Java 21 中,虚拟线程在 synchronized 内执行阻塞操作可能固定 Carrier;Native 或 Foreign Function 调用也可能固定。 JDK 24 的 JEP 491 改进了 monitor 行为,让虚拟线程因 synchronized 阻塞时通常能够卸载。因此“把所有 synchronized 改成 ReentrantLock”不是 Java 25 的通用迁移规则。

Java 25 仍需用 JFR 和线程转储观察 Native 调用、长临界区、Carrier 饱和与下游等待。Oracle 的 JDK 24 重要变化记录了 JEP 491 的行为变化; JEP 444定义了虚拟线程的设计目标和每任务一个线程模型。

观测大量虚拟线程

使用适合虚拟线程的新线程转储:

jcmd <PID> Thread.dump_to_file -format=json threads.json
jcmd <PID> JFR.start name=virtual settings=profile duration=30s filename=virtual.jfr

不要每个虚拟线程都记录高基数指标标签。以路由、任务类型和下游为维度聚合等待时间、并发数、失败和截止时间超限。

适用与不适用场景

虚拟线程适合大量相互独立、以阻塞等待为主的任务。CPU 密集计算仍受核心数限制,应使用受限执行器或 Fork/Join。基于事件 循环且已经稳定的系统不必仅为使用新特性重写。

下一步

阅读并发设计模式与确定性测试,用所有权和可控时序证明并发 不变量。


分享这篇文章:

上一篇
CPU 缓存、伪共享与 Disruptor
下一篇
并发设计模式与确定性测试