Project Loom已通过JEP 425进入JDK。它自 2022 年 9 月的 Java 19 起作为预览功能提供。其出现的目的就是为了显著减少编写、维护和提高并发应用程序的吞吐量的。

虚拟线程在何处有意义

轻量级虚拟线程引入成为开发人员开发应用程序时使用的一种令人兴奋的方法。过去几年表明,应用程序通过网络相互通信的成为越来越来流行的趋势。许多应用程序都使用数据存储、消息代理和远程服务。I/O 密集型应用程序是受益于虚拟线程的主要应用程序,如果它们构建为使用阻塞 I/O 设施(如同步 HTTP、数据库和消息代理客户端)。与平台线程相比,在虚拟线程上运行此类工作负载有助于减少内存占用,在某些情况下,虚拟线程可以提高并发性。

如果系统具有并发所需的额外资源,则可以实现更高的并发性。具体来说,这些是:

  1. 连接池中的可用连接
  2. 足够的内存来为增加的负载提供服务
  3. 未使用的 CPU 时间

虚拟线程的使用显然不仅限于直接减少内存占用或增加并发性。虚拟线程的引入还促使人们更广泛地重新审视在只有平台线程可用时为运行时做出的决策。

并发实用程序的修订

Spring 框架对提交的每个可运行对象使用一个新的平台线程(如果未配置)。这种安排需要创建平台线程,从而导致吞吐量降低和内存消耗增加。 可以修改为默认使用虚拟线程,以减少内存占用并增加其默认配置中的吞吐量。(同时,自定义变体可用于相同的效果。SimpleAsyncTaskExecutorThreadFactorySimpleAsyncTaskExecutorTaskExecutor

编程模型的修订

虚拟线程可以改变我们对异步编程接口的看法。在许多情况下,如果我们从代码在虚拟线程上运行的假设开始,那么使用异步编程模型的原因就会消失。虚拟线程的分配要轻量级得多,线程数不再是可伸缩性的主要限制。为了使事情更清楚,异步编程模型不会消除例如网络调用的延迟。异步 Apache HTTP 客户端或 netty 只是在网络调用无法进行时切换任务,而不是阻塞线程。虚拟线程也会发生同样的情况:它们有效地屈服于另一个可以随着工作进展的线程。Runnable

Project Loom 重新访问了 Java 运行时库中的所有区域,这些区域可以阻止和更新代码,以便在代码遇到阻塞时生成代码。Java 的并发性实用程序(例如、、)可以在虚拟线程上使用,而不会阻塞底层平台线程。此更改使 和 成为虚拟线程上的良好公民,并消除了对 Futures 的回调驱动使用的需求。ReentrantLockCountDownLatchCompletableFutureFuture.get().get(Long, TimeUnit)

导致异步 Servlet API 的假设可能会随着虚拟线程的引入而失效。引入异步 Servlet API 是为了释放服务器线程,以便服务器可以在工作线程继续处理请求时继续为请求提供服务。在虚拟线程上运行 servlet 请求和响应处理消除了释放服务器线程的需要,从而导致了为什么要使用的问题,因为异步分叉涉及大量状态保存,可以消除,因为它不再需要。ServletRequest.startAsync()

缓解限制

我们的团队一直在试验虚拟线程,因为它们被称为光纤。从那时起,随着Java 19的发布,一个限制很普遍,导致平台线程固定,有效地降低了使用时的并发性。代码块的使用本身不是问题;仅当这些块包含阻塞代码时,通常说 I/O 操作。这些安排可能会出现问题,因为运营商平台线程是有限的资源,并且在没有仔细检查工作负载的情况下在虚拟线程上运行代码时,平台线程固定可能会导致应用程序性能下降。事实上,即使没有虚拟线程,同步块中的相同阻塞代码也可能导致性能问题。synchronizedsynchronized

Spring 框架大量使用来实现锁定,主要是围绕本地数据结构。多年来,在虚拟线程可用之前,我们已经修改了可能与第三方资源交互的块,消除了高并发应用程序中的锁争用。因此,由于Spring庞大的社区和来自现有并发应用程序的广泛反馈,Spring已经处于相当不错的状态。在成为虚拟线程方案中的最佳公民的道路上,我们将进一步重新审视 I/O 或其他阻塞代码上下文中的使用情况,以避免平台线程固定在热代码路径中,以便您的应用程序可以充分利用 Project Loom。synchronizedsynchronizedsynchronized

在虚拟线程上运行 Spring 应用程序

使用最新版本的Spring Framework,Spring Boot和Apache Tomcat,您可以自己开始实验。您开始分析虚拟线程如何影响应用程序工作负载,并比较虚拟线程使用情况与平台线程使用情况。要定制 Spring 引导应用程序以处理虚拟线程上的 servlet 请求,请应用以下定制:

@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
public AsyncTaskExecutor asyncTaskExecutor() {
  return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}

@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
  return protocolHandler -> {
    protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
  };
}

我们正在尽一切努力使预览体验暂时尽可能无缝,我们希望在Loom在新的OpenJDK版本中退出预览版后提供一流的配置选项。

如果我们了解了核心框架中面向虚拟线程的优化的具体潜力,无论是某些使用点还是某些使用,我们将尝试在即将推出的 Spring 框架和 Spring Boot 维护版本中尽可能将相应的改进纳入,甚至在 Loom 正式发布之前。synchronizedThreadLocal

虚拟线程不仅影响 Spring 框架,还影响所有周围的集成,例如数据库驱动程序、消息传递系统、HTTP 客户端等等。其中许多项目都意识到需要改善他们的行为,以释放Project Loom的全部潜力。synchronized

您的应用程序是否会从虚拟线程中受益?

这是一个比好处更具体的问题,也是一个更难回答的问题。

我们可以说的是,在不进行任何更改的情况下,您最可能受益的情况是,如果您当前根本没有做任何异步操作(甚至没有 Servlet 3.1 样式的异步请求,否则您可能需要进行一些修订以更好地对齐)。当然,必须有一些实际的I / O或其他线程停放,Loom才能带来好处。

我们还认为,ReactiveX 风格的 API 仍然是编写并发逻辑的强大方式,也是处理流的自然方式。我们看到虚拟线程补充了反应式编程模型,消除了阻塞 I/O 的障碍,同时纯粹使用虚拟线程处理无限流仍然是一个挑战。ReactiveX 是声明性并发(如分散-收集)很重要的并发方案的正确方法。底层反应流规范定义了数据管道的需求、背压和取消协议,而不将自身限制为非阻塞 API 或特定的线程用法。