沉默是金,总会发光
大家好,我是沉默
如果你写过 Java 后端,大概率经历过这些场景:
凌晨 2 点,线上报警:
线程池满了
请求排队
接口超时
你只能开始经典三连:
corePoolSize 调大一点
maxPoolSize 再调大一点
队列长度再放大一点
结果第二天又报警了。
然后团队开始研究:
-
NIO
-
Netty
-
CompletableFuture
-
WebFlux
结果代码越来越复杂:
.thenApply()
.thenCompose()
.thenAccept()
一旦出 bug,堆栈直接变成:
Callback Hell
根本找不到业务逻辑。
问题的根源其实只有一个:
Java 线程太贵了。
直到 JDK 21 发布了一个重磅特性:
虚拟线程(Virtual Threads)
它来自 Project Loom。
很多 Java 架构师看到后只有一句话:
这可能是 Java 并发模型 20 年最大升级。
**-**01-
为什么需要虚拟线程?
在 JDK 21 之前,Java 的并发模型是:
一个请求 = 一个线程
也就是:
Thread-per-Request
例如 Web 服务:
HTTP Request -> Tomcat Thread -> Business Logic
看起来简单,但问题巨大。
痛点一:平台线程非常昂贵
JDK 21 之前的线程叫:
平台线程(Platform Thread)
它直接映射到:
OS 内核线程
意味着:
| 问题 | 原因 |
|---|---|
| 内存占用大 | 每个线程栈 1MB ~ 2MB |
| 切换成本高 | 内核态 / 用户态切换 |
| 数量有限 | 几千线程就顶天 |
如果你创建:
10000线程
服务器很可能直接:
内存爆炸
CPU上下文切换爆炸
痛点二:异步编程变成噩梦
为了提升并发能力,我们不得不写:
- NIO
- Netty
- Reactive
- WebFlux
例如:
Mono.just(data)
.flatMap(service::query)
.flatMap(service::process)
.map(service::convert)
代码变成:
回调地狱
调试堆栈:
ReactiveOperator
Publisher
Subscriber
业务代码直接淹没。
于是 Java 社区一直在思考一个问题:
能不能既保持同步代码,又获得异步性能?
答案就是:
虚拟线程。
- 02-
什么是虚拟线程?
虚拟线程(Virtual Thread)本质是:
由 JVM 管理的轻量级线程。
它不再 1:1 映射到操作系统线程。
而是:
M : N
很多虚拟线程复用少量 OS 线程。
平台线程 vs 虚拟线程
| 特性 | 平台线程 | 虚拟线程 |
|---|---|---|
| 管理者 | OS 内核 | JVM |
| 映射关系 | 1:1 | M:N |
| 创建成本 | 高 | 极低 |
| 内存占用 | MB级 | 几百字节 |
| 最大数量 | 几千 | 几百万 |
简单理解:
虚拟线程 ≈ Java 协程
非常类似 Go 的 Goroutine。
虚拟线程到底是怎么工作的?
我们用一个非常经典的比喻:
出租车模型
假设:
CPU = 10 辆出租车
请求 = 10000 个乘客
传统线程模式
流程是:
乘客上车 -> 处理请求
但如果中间遇到:
数据库查询
网络调用
sleep
出租车就会:
停在路边等待
结果:
10辆车全部卡死
9990乘客排队
这就是:
线程阻塞
虚拟线程模式
JVM做了一件神奇的事情:
Mount(挂载)
任务开始执行:
虚拟线程 -> 载体线程
Unmount(卸载)
如果遇到 I/O:
数据库
网络
sleep
JVM 会:
保存执行状态
让线程下车
然后:
载体线程继续干别的任务
Resume(恢复)
I/O完成后:
恢复执行
仿佛什么都没发生。
最终效果:
10线程
处理10000请求
CPU 利用率被榨干。
- 03-
如何使用虚拟线程
JDK 21 使用虚拟线程非常简单。
创建虚拟线程
Thread.startVirtualThread(() -> {
System.out.println(Thread.currentThread());
});
或者:
Thread.ofVirtual()
.name("vthread")
.start(() -> {
// business logic
});
替代线程池(重点)
过去:
Executors.newFixedThreadPool(200)
现在推荐:
try (varexecutor=Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10000)
.forEach(i -> executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
returni;
}));
}
核心变化:
任务 = 线程
线程 = 用完就丢
不再需要线程池。
虚拟线程到底有多强?
实验:
10000任务
每个 sleep 1 秒
平台线程池
线程数 = 200
执行:
50秒
原因:
10000 / 200 = 50 批
虚拟线程
执行时间:
≈1 秒
因为:
10000虚拟线程同时挂起
载体线程不断复用
性能对比
| 指标 | 平台线程池 | 虚拟线程 |
|---|---|---|
| 任务数 | 10000 | 10000 |
| 执行时间 | ~50s | ~1s |
| 并发能力 | 受线程限制 | 极高 |
| 内存占用 | 高 | 极低 |
提升:
40倍+
适用场景
虚拟线程不是万能药。
适合场景(I/O密集型)
例如:
Web服务
微服务
RPC调用
数据库访问
API网关
这些场景的特点:
大量等待
少量计算
虚拟线程能极大提升吞吐量。
不适合场景(CPU密集)
例如:
视频转码
加密计算
AI计算
科学计算
原因很简单:
CPU核心数是固定的
虚拟线程无法增加 CPU。
**-****04-**总结
两个大坑(必须注意)
坑一:Pinning(线程钉住)
如果虚拟线程在:
synchronized
native 方法
内部发生阻塞:
虚拟线程无法卸载
直接退化成:
平台线程
例如:
synchronized(lock) {
Thread.sleep(1000);
}
这是错误写法。
建议:
ReentrantLock
坑二:ThreadLocal 内存爆炸
以前:
线程池 = 200线程
ThreadLocal 不会占太多。
现在:
虚拟线程 = 100万
如果每个线程:
1MB ThreadLocal
服务器直接:
OOM
解决方案:
减少 ThreadLocal。
未来趋势
虚拟线程只是开始。
它的最佳搭档是:
结构化并发(Structured Concurrency)
未来 Java 并发模型可能会变成:
虚拟线程 + 结构化并发
代码会变得:
更简单
更安全
更高性能
JDK 21 的虚拟线程,可以用一句话概括:
用同步代码,写出异步性能。
核心:
-
虚拟线程是 Java 并发 20 年最大升级
-
非常适合 I/O 密集型系统
-
可以极大提升 系统并发能力
-
未来 Java 高并发架构将全面拥抱它
如果你还在写:
线程池调参
CompletableFuture
Reactive
那么现在是时候:
升级到 JDK 21 了。
热门文章
一套能保命的高并发实战指南
架构师必备:用 AI 快速生成架构图
**-****05-**粉丝福利
我这里创建一个程序员成长&副业交流群,
和一群志同道合的小伙伴,一起聚焦自身发展,
可以聊:
技术成长与职业规划,分享路线图、面试经验和效率工具,
探讨多种副业变现路径,从写作课程到私活接单,
主题活动、打卡挑战和项目组队,让志同道合的伙伴互帮互助、共同进步。
如果你对这个特别的群,感兴趣的,
可以加一下, 微信通过后会拉你入群,
但是任何人在群里打任何广告,都会被我T掉。