打开网易新闻 查看精彩图片

Java 21 作为 LTS 版本普及也有一段时间了。

最近做系统底层的重构,开会讨论了一个话题我觉得挺有意思,说既然现在有了虚拟线程 Virtual Threads ,随随便便就能创建几十上百万个,那以前写的那堆复杂的线程池配置是不是可以全删了?

这个问题其实非常有代表性,我们很容易被什么 轻量级百万并发 这些测试数据给镇住,有一种错觉:虚拟线程是多线程并发的终极方案。

但如果真的在生产环境把所有 ThreadPoolExecutor 都简单粗暴地替换成虚拟线程,我敢保证,系统大概率会在线上崩。

为什么要有线程池

要弄明白线程池会不会淘汰,首先得搞清楚当初为什么要有线程池。

Java 21 之前,Java 里的线程也就是平台线程,是直接映射到操作系统底层线程的。创建一个系统线程非常昂贵,需要分配 1MB 左右的栈空间,还要经历用户态到内核态的切换。因为贵,所以我们得把它缓存起来,反复利用,这就是线程池的核心逻辑。

但虚拟线程不同,它是在 JVM 层面实现的,创建极其廉价,占用的内存通常只有几百字节,创建时间是微秒级的。

绝对不要池化虚拟线程。

因为虚拟线程的创建成本和创建一个普通的 Java 对象,比如 new String 差不多。我们写代码的时候会去搞一个String 对象池,把用完的字符串留着下次用吗?

不会。用完即毁,让垃圾回收器去处理才是最高效的。

从这个角度上说,传统意义上为了减少创建和销毁开销而存在的线程池,在 I/O 密集型场景下,确实可以淘汰了。JDK 也推荐直接用

Executors.newVirtualThreadPerTaskExecutor

,也就是每个任务直接起一个新的虚拟线程。

线程池天然限流器
打开网易新闻 查看精彩图片
线程池天然限流器

线程池还是个天然的并发限流器。

假设你的 Tomcat 线程池配了 200 个核心线程,这意味着不管前端打过来多少请求,系统同时在处理的请求最多只有 200 个。这 200 个线程往下游调用数据库,你的数据库连接池只需要配 50-100 个就能稳稳接住。

现在如果把 Tomcat 换成了虚拟线程模式。

双十一大促,瞬间打过来 50000 个请求,JVM 毫秒间为你创建了 50000 个虚拟线程。

看起来 JVM 扛住了,但是接下来这 50000 个业务逻辑并发去请求你的 MySQL,去请求你的 Redis,去调用下游其他微服务。下游服务根本扛不住 50000 的并发,直接熔断宕机。

虚拟线程只解决了 服务本身不被 I/O 挂起 的问题,但它没有解决 下游资源的物理瓶颈 问题。

以前平台线程池是一道物理防御机制,扛不住了就把请求扔到队列里,或者直接拒绝 Reject 。现在物理防御没了,如果你不加额外的并发控制,灾难就会向下游蔓延。

即使不用线程池,也必须在代码里引入 Semaphore 信号量或者其他的限流组件,来保护那些极其脆弱的稀缺资源。这种并发控制的思维,是永远不会淘汰的。

 虚拟线程的不适合场景
打开网易新闻 查看精彩图片
虚拟线程的不适合场景

我们要清晰地认识到虚拟线程的工作原理,虚拟线程遇到 I/O 阻塞,比如发 HTTP 请求、读写文件这些耗时操作,JVM 会把它从底层的载体线程上卸载下来,让载体线程去跑别的虚拟线程。

注意前提:遇到 I/O 阻塞。

如果扔给虚拟线程的是一个纯计算任务,比如加密解密、图片压缩、复杂的大集合排序。虚拟线程就不会挂起,它会死死占着底层的载体线程不放。

底层载体线程的数量默认等于机器的 CPU 核心数。如果这台机器有 8 个核,只要有 8 个做着密集计算的虚拟线程跑起来,其他的几万个虚拟线程就只能排队干瞪眼,整个系统会陷入一种诡异的 假死 状态。

对于这种 CPU 密集型的任务,虚拟线程没有任何优势,甚至因为多了一层调度逻辑,性能反而会下降一点点。

这时就需要老老实实地建一个 ForkJoinPool 或者专门配置过的 ThreadPoolExecutor ,把线程数设置为 CPU核心数 + 1 ,用专门的平台线程池去处理这些重度计算任务。

 Pinning 固定问题
打开网易新闻 查看精彩图片
Pinning 固定问题

最后提一个实际使用中极容易踩的坑。

Java 21 如果在 synchronized 代码块里发生了 I/O 阻塞,虚拟线程是无法被卸载的!!!

这会导致它将底层的载体线程“死死钉住”(Pinning)。虽然后续的 Java 版本也在底层做了大量修复,但历史遗留的第三方库里充斥着大量的 synchronized

如果依赖的某个老旧 SDK 在关键路径上用了重量级锁并发生了阻塞,一波高并发过来,底层的几个平台线程全被 Pin 住,那你的虚拟线程池直接就瘫痪了。

应对这种旧系统的整合,传统的线程池依然是最稳妥的隔离方案。

说在最后

并发编程的本质,对资源瓶颈的敬畏、对系统边界的控制,从未因为任何新技术的出现而改变。

虚拟线程是个伟大的技术,不过现实业务中,从系统稳定性考量还是没有线程池方案稳!

打开网易新闻 查看精彩图片