第一步、使用@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 驱动程序。