Java 21 作为 ** 长期支持(LTS)版本,带来了众多重磅新特性,其中虚拟线程(Virtual Threads)** 无疑是最受开发者关注的核心功能之一。它彻底改变了 Java 传统线程的性能瓶颈,让高并发编程变得更简单、高效,无需复杂的线程池、异步框架就能实现百万级并发。
本文将从虚拟线程核心原理、与平台线程的区别、基础用法、实战场景、性能优势等维度,带你彻底吃透 Java 21 虚拟线程,看完就能直接落地项目。
一、传统线程(平台线程)的痛点
在 Java 21 之前,我们使用的线程都是平台线程(Platform Threads) ,本质是对操作系统内核线程的封装。
它的致命问题:
- 资源昂贵:操作系统线程是稀缺资源,创建、销毁、切换开销极大,一个 JVM 进程最多支撑几千~几万平台线程。
- 并发受限:高并发场景下,线程池满、阻塞、上下文切换频繁,导致服务吞吐量上不去。
- 编程复杂:为了优化性能,必须手写线程池、异步回调、CompletableFuture 等,代码可读性差、维护成本高。
简单说:平台线程 = 重量级资源,数量少、开销大、难管理。
二、虚拟线程是什么?
虚拟线程是 JVM 管理的轻量级线程 ,不直接绑定操作系统内核线程,属于用户态线程。
核心特点:
- 极轻量 :一个虚拟线程仅占用几百字节内存,JVM 可以轻松创建百万级虚拟线程。
- 无池化:用完即销毁,无需手动管理线程池,代码更简洁。
- 兼容现有 API :完全兼容
java.lang.Thread、ExecutorService等原有线程 API,零成本迁移。 - 高效调度 :JVM 负责调度虚拟线程,只有在虚拟线程执行代码时,才会占用平台线程(挂载 / 卸载机制)。
一句话总结:虚拟线程 = 轻量级、海量、易用、高性能的并发编程解决方案。
三、虚拟线程 VS 平台线程:核心区别
表格
| 特性 | 平台线程(传统线程) | 虚拟线程(Java 21+) |
|---|---|---|
| 归属 | 操作系统内核管理 | JVM 自身管理 |
| 内存开销 | 巨大(MB 级) | 极小(KB / 字节级) |
| 并发数量 | 几千~几万 | 百万 + |
| 线程池 | 必须使用,否则 OOM | 无需线程池,用完即弃 |
| 上下文切换开销 | 极高(内核态切换) | 极低(用户态切换) |
| 编程复杂度 | 高(线程池、异步、锁优化) | 低(同步代码写高并发) |
| 适用场景 | 低并发、CPU 密集型 | 高并发、I/O 密集型(核心场景) |
关键结论:I/O 密集型业务(接口请求、数据库操作、消息消费、文件读写),虚拟线程性能碾压平台线程!
四、虚拟线程核心原理:挂载与卸载
虚拟线程的高性能,核心依赖M:N 调度模型:
- M 个虚拟线程
- 挂载到 N 个平台线程上执行
机制流程:
- 虚拟线程运行时,临时挂载到一个平台线程(称为载体线程)。
- 当虚拟线程执行阻塞 I/O 操作 (如 sleep、网络请求、DB 查询)时,JVM 会自动将其卸载,释放平台线程给其他虚拟线程使用。
- I/O 完成后,虚拟线程会重新挂载到任意空闲平台线程继续执行。
整个过程无内核态切换、无操作系统调度开销,这就是虚拟线程能实现百万并发的核心!
五、虚拟线程实战:3 种常用创建方式
Java 21 提供了极简 API 创建虚拟线程,完全兼容原有语法。
方式 1:使用 Thread.startVirtualThread ()(最简洁)
直接创建并启动虚拟线程,一行代码搞定:
java
运行
public class VirtualThreadDemo {
public static void main(String[] args) {
// 创建并启动虚拟线程
Thread.startVirtualThread(() -> {
System.out.println("虚拟线程执行:" + Thread.currentThread().getName());
});
}
}
方式 2:使用 Thread.ofVirtual () 工厂类
支持自定义线程名、继承性等,适合需要配置的场景:
java
运行
public class VirtualThreadDemo {
public static void main(String[] args) throws InterruptedException {
// 创建虚拟线程工厂(自定义名称)
ThreadFactory factory = Thread.ofVirtual().name("my-virtual-thread-", 0).factory();
// 创建虚拟线程
Thread virtualThread = factory.newThread(() -> {
System.out.println("自定义虚拟线程执行:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
// 启动线程
virtualThread.start();
// 等待线程执行完成
virtualThread.join();
}
}
方式 3:使用 Executors.newVirtualThreadPerTaskExecutor ()(推荐实战)
这是企业开发最常用方式,完全替代线程池,自动为每个任务创建虚拟线程:
java
运行
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class VirtualThreadExecutorDemo {
public static void main(String[] args) {
// 创建虚拟线程执行器(无上限,自动管理)
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
// 提交 10000 个并发任务
for (int i = 0; i < 10000; i++) {
int taskNum = i;
executor.submit(() -> {
System.out.println("任务 " + taskNum + " 执行,线程:" + Thread.currentThread().getName());
try {
// 模拟 I/O 阻塞(虚拟线程无开销)
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
} // 自动关闭线程池,无需手动 shutdown
}
}
优势:
- 无需配置核心线程数、最大线程数、队列
- 自动管理生命周期,代码极简
- 高并发下性能远超任何线程池
六、虚拟线程实战:高并发 I/O 场景测试
我们做一个对比测试:分别用平台线程池、虚拟线程执行 10000 个 I/O 阻塞任务,看耗时差异。
1. 平台线程池(传统方式)
java
运行
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class PlatformThreadTest {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
// 固定 200 线程池(超过会阻塞)
try (ExecutorService executor = Executors.newFixedThreadPool(200)) {
for (int i = 0; i < 10000; i++) {
executor.submit(() -> {
try {
// 模拟 I/O 阻塞(如 HTTP/DB 请求)
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
long end = System.currentTimeMillis();
System.out.println("平台线程总耗时:" + (end - start) + "ms");
}
}
2. 虚拟线程(Java 21 方式)
java
运行
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class VirtualThreadTest {
public static void main(String[] args) {
long start = System.currentTimeMillis();
// 虚拟线程执行器
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10000; i++) {
executor.submit(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
long end = System.currentTimeMillis();
System.out.println("虚拟线程总耗时:" + (end - start) + "ms");
}
}
测试结果(参考)
- 平台线程池:≈5000ms+
- 虚拟线程:≈120ms
性能差距达到 40 倍 +!这就是虚拟线程在 I/O 密集型场景的威力。
七、虚拟线程使用注意事项(避坑指南)
虚拟线程虽强,但不能滥用,以下是开发必须注意的点:
1. 不适合 CPU 密集型任务
虚拟线程的优势是阻塞时释放载体线程,如果任务是纯 CPU 计算(无阻塞),虚拟线程无法卸载,性能和平台线程一致,甚至略有开销。
适用场景 :I/O 密集型(90% 企业业务:接口、DB、MQ、文件、HTTP)不适用场景:CPU 密集型(大数据计算、加密、视频编解码)
2. 不要池化虚拟线程
官方明确:虚拟线程极轻量,用完即销毁,绝对不要放入线程池!
错误用法:
java
运行
// 错误!虚拟线程无需池化,画蛇添足
ExecutorService executor = new ThreadPoolExecutor(
100, 200, 60L, TimeUnit.SECONDS,
new SynchronousQueue<>(),
Thread.ofVirtual().factory()
);
正确用法:直接用 Executors.newVirtualThreadPerTaskExecutor()
3. 避免使用 ThreadLocal
虚拟线程数量极大,ThreadLocal 会为每个虚拟线程存储数据,极易造成内存泄漏。
建议:使用 ScopedValue(Java 21 新特性)替代 ThreadLocal。
4. 同步代码块 / 方法谨慎使用
如果虚拟线程内有长时间持有锁的代码,会阻塞载体线程,降低并发性能。
优化:锁内只做轻量操作,避免 I/O 操作。
八、虚拟线程在项目中的落地建议
-
新项目直接使用:Java 21 LTS + 虚拟线程,替代所有手动线程池。
-
老项目逐步迁移:将 I/O 密集型模块(如接口请求、消息消费)替换为虚拟线程。
-
搭配框架使用 :Spring Boot 3.2+ 已原生支持虚拟线程,只需配置:
properties
spring.threads.virtual.enabled=true -
监控优化 :使用 JDK 自带的
jstack、jconsole监控虚拟线程状态。
九、总结插入广告:各行各业学习千款源码就上:svipm.com.cn
虚拟线程是 Java 并发编程的革命性升级 ,它解决了传统线程的性能瓶颈,让同步代码实现百万级高并发成为现实。
核心亮点回顾:
- 轻量无开销:百万线程轻松创建,内存占用极低。
- 代码极简:无需线程池、异步框架,同步写法写高并发。
- 性能炸裂:I/O 密集型场景性能提升 10~100 倍。
- 兼容原生 API:零学习成本、零迁移成本。
Java 21 虚拟线程,绝对是未来高并发编程的标配,建议所有开发者尽快学习落地!