Loom会造成CPU密集型线程的不公平调度


Project Loom ( JEP 425 ) 可能是 Java 有史以来最受期待的新增功能之一。它对虚拟线程(或“绿色线程”)的实现保证了开发人员能够创建高度并发的应用程序,例如具有数十万个打开的 HTTP 连接,坚持众所周知的线程每个请求的编程模型,而无需诉诸不太熟悉且通常更复杂的反应式方法。

经过几年的努力,Loom最近才被合并到 OpenJDK 的主线中,并在最新的[url=https://jdk.java.net/19/]Java 19 早期访问版本[/url]中作为预览功能提供。即,现在是接触虚拟线程并探索新功能的最佳时机。

虚拟线程由 JVM调度到操作系统级别的载体线程上。如果应用程序代码遇到阻塞方法,Loom 会从当前的载体上卸载虚拟线程,为其他虚拟线程腾出空间进行调度。
虚拟线程很便宜并且由 JVM 管理,也就是说,您可以拥有很多,甚至数百万。

那么, Loom 的调度器如何知道一个方法正在阻塞?事实证明,它没有。
从Project Loom 的主要作者Ron Pressler那里了解到,情况正好相反:JDK 中的阻塞方法已经针对 Loom 进行了调整,以便在被虚拟线程调用时释放 OS 级别的载体线程:

Java 中的所有阻塞都是通过 JDK 完成的(除非您显式调用本机代码)。我们将 JDK 中的“叶leaf”阻塞方法更改为阻塞虚拟线程而不是平台线程。例如,在所有 java.util.concurrent 中只有一种这样的方法:LockSupport.park

如果代码不是 IO-boundIO密集型,而是 CPU-bound CPU密集型,会发生什么?即,如果虚拟线程中的代码在没有调用任何 JDK 的“叶leaf”阻塞方法的情况下运行一些繁重的计算,那么虚拟线程会怎样?

假设有一组worker执行 CPU 密集型任务,因为没有yield点。您在不同的虚拟线程上启动了许多任务。您希望它们都在同一时间完成。在这种情况下它们不会在同一时间完成,并且完成的时间将随机地大不相同。

在虚拟线程和操作系统级线程的情况下,CPU密集的代码的行为实际上是非常不同的:
有的虚拟线程会一直在执行,而其他虚拟线程则会发生类似被堵塞的现象,等待这些虚拟线程执行一段时间才执行,而不是每段虚拟线程都能公平地轮流被执行。

当然,这种情况只在有超过CPU线程水平的并发全占据CPU的情况下才会发生,不过这还是这引起了大家对公平性的担忧,但这不是一种常见的情况,而且在这种边缘情况下不使用虚拟线程是可以避免的。

在这种情况下,java开发者需要成为一定水平的专家,了解Loom适合哪些使用情况,哪些不适合,这大大增加了错误的概率。
为什么不让Loom在所有情况下都能很好地更容易地工作呢,就像Go语言一样?