Quarkus中虚拟线程


第一步、使用@RunOnVirtualThread注释实现虚拟线程。@RunOnVirtualThread 注解指示 Quarkus 在一个新的虚拟线程而不是当前线程上调用注解的方法。Quarkus 会处理虚拟线程的创建和卸载。

由于虚拟线程是一次性实体,@RunOnVirtualThread 的基本思想是在新的虚拟线程上卸载端点处理程序的执行,而不是在事件循环或工作线程上运行(在 RESTEasy Reactive 的情况下)。

为此,只需向端点添加 @RunOnVirtualThread 注解即可。如果用于运行应用程序的 Java 虚拟机提供虚拟线程支持(Java 19 或更高版本),那么端点的执行就会被卸载到虚拟线程中。这样就可以执行阻塞操作,而不会阻塞虚拟线程所在的平台线程。

对于 RESTEasy Reactive,该注解只能用于注解为 @Blocking 或因其签名而被视为阻塞的端点。


第二步、确保您使用的是 Java 19+ 版本,加入:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
<properties>
    <maven.compiler.source>19</maven.compiler.source>
    <maven.compiler.target>19</maven.compiler.target>
</properties>

最后,在 Java 21 之前,您需要使用 --enable-preview 标志配置编译器插件。

<plugin>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>${compiler-plugin.version}</version>
  <configuration>
    <compilerArgs>
      <arg>--enable-preview</arg>
      <arg>-parameters</arg>
    </compilerArgs>
  </configuration>
</plugin>

代码案例:
下面的示例显示了三个端点之间的差异,它们都是查询数据库中的财富,然后返回给客户端。

  • 第一个端点使用传统的阻塞方式,由于其签名而被认为是阻塞的。
  • 第二个端点使用 Mutiny,由于其签名被认为是非阻塞的。
  • 第三个使用 Mutiny,但采用同步方式,因为它不返回 "反应式类型",所以被认为是阻塞的,可以使用 @RunOnVirtualThread 注解。

package org.acme.rest;

import org.acme.fortune.model.Fortune;
import org.acme.fortune.repository.FortuneRepository;
import io.smallrye.common.annotation.RunOnVirtualThread;
import io.smallrye.mutiny.Uni;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.util.List;
import java.util.Random;


@Path("")
public class FortuneResource {

    @Inject FortuneRepository repository;

    @GET
    @Path(
"/blocking")
    public Fortune blocking() {
       
// Runs on a worker (platform) thread
        var list = repository.findAllBlocking();
        return pickOne(list);
    }

    @GET
    @Path(
"/reactive")
    public Uni<Fortune> reactive() {
       
// Runs on the event loop
        return repository.findAllAsync()
                .map(this::pickOne);
    }

    @GET
    @Path(
"/virtual")
    @RunOnVirtualThread
    public Fortune virtualThread() {
       
// Runs on a virtual thread
        var list = repository.findAllAsyncAndAwait();
        return pickOne(list);
    }

}

使用虚拟线程友好的客户端
Java 生态系统尚未完全准备好使用虚拟线程。因此,您需要小心谨慎,尤其是在使用执行 I/O 的库时。

幸运的是,Quarkus 提供了一个可用于虚拟线程的庞大生态系统。Quarkus中使用的反应式编程库Mutiny和Vert.x Mutiny绑定提供了编写阻塞代码的能力(所以,不用担心,没有学习曲线),而这些代码不会固定在载体线程上。

因此

  • 在反应式 API 的基础上提供阻塞式 API 的 Quarkus 扩展可以在虚拟线程中使用。这包括反应式 rest 客户端、redis 客户端、邮件发送器......
  • 返回 Uni 的 API 可直接使用 uni.await().atMost(...)。它会阻塞虚拟线程,而不会阻塞载体线程,还能通过简单的(非阻塞)超时支持提高应用程序的弹性。
  • 如果使用 Mutiny 绑定的 Vert.x 客户端,可使用 andAwait() 方法阻塞虚拟线程,直到得到结果,而无需锁定载体线程。它包括所有反应式 SQL 驱动程序。