RESTアプリケーションで仮想スレッドを使用
このガイドでは、REST アプリケーションで仮想スレッドを使用する方法を説明します。 仮想スレッドは I/O に関するものなので、REST クライアントも使用します。
前提条件
このガイドを完成させるには、以下が必要です:
-
約15分
-
IDE
-
JDK 17+がインストールされ、
JAVA_HOMEが適切に設定されていること -
Apache Maven 3.9.12
-
使用したい場合は、 Quarkus CLI
-
ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること
アーキテクチャ
このガイドに組み込まれているアプリケーションは非常にシンプルです。 2つの都市(フランスのヴァランスとギリシャのアテネ)の気象サービスを呼び出し、現在の気温に基づいて最適な場所を決定します。
Maven プロジェクトの作成
まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。
Windowsユーザーの場合:
-
cmdを使用する場合、(バックスラッシュ
\を使用せず、すべてを同じ行に書かないでください)。 -
Powershellを使用する場合は、
-Dパラメータを二重引用符で囲んでください。例:"-DprojectArtifactId=rest-virtual-threads"
このコマンドは、Quarkus REST(旧RESTEasy Reactive)、RESTクライアント、 Jackson エクステンションをインポートする新しいプロジェクトを生成し、特に以下の依存関係を追加します:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client-jackson</artifactId>
</dependency>
implementation("io.quarkus:quarkus-rest-jackson")
implementation("io.quarkus:quarkus-rest-client-jackson")
|
なぜ リアクティブ エクステンションを使うのか不思議に思うかもしれません。 仮想スレッドには限界があり、リアクティブエクステンションを使用する場合にのみ適切に統合することができます。 ご心配なく。あなたのコードは100%同期/命令型スタイルで書かれます。 詳しくは 仮想スレッドリファレンスガイド をご確認ください。 |
pom.xml ファイルの準備
仮想スレッドを使うためには、 pom.xml ファイルをカスタマイズする必要があります。
1) <maven.compiler.release>17</maven.compiler.release> の行を探し、次のように置換します:
<maven.compiler.release>21</maven.compiler.release>
2) maven-surefire-pluginおよびmaven-failsafe-plugin設定で、次の argLine パラメータを追加します:
The -Djdk.tracePinnedThreads system property only works on Java 21-23.
It has been removed in Java 24+. For Java 24+, use the JFR events or Quarkus testing extensions instead (see the Testing virtual thread applications section).
|
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
<argLine>-Djdk.tracePinnedThreads</argLine> <!-- Added line - only works on Java 21-23 -->
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
<argLine>-Djdk.tracePinnedThreads</argLine> <!-- Added line - only works on Java 21-23 -->
</configuration>
</execution>
</executions>
</plugin>
The -Djdk.tracePinnedThreads will detect pinned carrier threads while running tests on Java 21-23 (See the virtual thread reference guide for details).
|
Java 19 と 20 における --enable-preview
Java 19 または 20 を使用している場合は、 |
天気クライアントの作成
このセクションは仮想スレッドに関するものではありません。 仮想スレッドの使い方を実証するためにI/Oを行う必要があるため、I/O操作を行うクライアントが必要だからです。 さらに、RESTクライアントは仮想スレッドと親和的です。ピンを行わず、伝搬を正しく行います。
以下の内容で src/main/java/org/acme/WeatherService.java クラスを作成します:
package org.acme;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.quarkus.rest.client.reactive.ClientQueryParam;
import jakarta.ws.rs.GET;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.reactive.RestQuery;
@RegisterRestClient(baseUri = "https://api.open-meteo.com/v1/forecast")
public interface WeatherService {
@GET
@ClientQueryParam(name = "current_weather", value = "true")
WeatherResponse getWeather(@RestQuery double latitude, @RestQuery double longitude);
record WeatherResponse(@JsonProperty("current_weather") Weather weather) {
// represents the response
}
record Weather(double temperature, double windspeed) {
// represents the inner object
}
}
このクラスは気象サービスとの HTTP インタラクションをモデル化します。 RESTクライアントについて詳しくは、専用の ガイド を参照してください。
HTTPエンドポイントの作成
次に、以下の内容で src/main/java/org/acme/TheBestPlaceToBeResource.java クラスを作成します:
package org.acme;
import io.smallrye.common.annotation.RunOnVirtualThread;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RestClient;
@Path("/")
public class TheBestPlaceToBeResource {
static final double VALENCE_LATITUDE = 44.9;
static final double VALENCE_LONGITUDE = 4.9;
static final double ATHENS_LATITUDE = 37.9;
static final double ATHENS_LONGITUDE = 23.7;
@RestClient WeatherService service;
@GET
@RunOnVirtualThread (1)
public String getTheBestPlaceToBe() {
var valence = service.getWeather(VALENCE_LATITUDE, VALENCE_LONGITUDE).weather().temperature();
var athens = service.getWeather(ATHENS_LATITUDE, ATHENS_LONGITUDE).weather().temperature();
// Advanced decision tree
if (valence > athens && valence <= 35) {
return "Valence! (" + Thread.currentThread() + ")";
} else if (athens > 35) {
return "Valence! (" + Thread.currentThread() + ")";
} else {
return "Athens (" + Thread.currentThread() + ")";
}
}
}
| 1 | 仮想スレッド上でこのメソッドを呼び出すようQuarkusに指示します。 |
アプリケーションを開発モードで実行する
仮想スレッドをサポートするOpenJDKとJVMのバージョンを使用し、 ./mvnw quarkus:dev で devモード を起動しているようにして下さい:
> java --version
openjdk 21 2023-09-19 LTS (1)
OpenJDK Runtime Environment Temurin-21+35 (build 21+35-LTS)
OpenJDK 64-Bit Server VM Temurin-21+35 (build 21+35-LTS, mixed mode)
> ./mvnw quarkus:dev (2)
| 1 | 19以上必須、21以上を推奨 |
| 2 | 開発モードを起動する |
次に、ブラウザで http://localhost:8080 を開きます。 次のようなメッセージが表示されるはずです:
Valence! (VirtualThread[#144]/runnable@ForkJoinPool-1-worker-6)
ご覧のように、エンドポイントは仮想スレッド上で実行されます。
舞台裏で何が起こったのかを理解することが不可欠です:
-
Quarkusは、(
@RunOnVirtualThreadアノテーションのため)エンドポイントを呼び出すための仮想スレッドを作成します。 -
コードがRESTクライアントを呼び出すと、仮想スレッドはブロックされますが、キャリア・スレッドはブロックされません(これが仮想スレッドの マジック・タッチ です)。
-
RESTクライアントの最初の呼び出しが完了すると、仮想スレッドは再スケジュールされ、実行を継続します。
-
2回目のRESTクライアント呼び出しが発生し、仮想スレッドは再びブロックされます(キャリアスレッドはブロックされません)。
-
最後に、RESTクライアントの2回目の呼び出しが完了すると、仮想スレッドは再スケジュールされ、実行を継続します。
-
メソッドは結果を返します。仮想スレッドは終了します。
-
結果はQuarkusによってキャプチャされ、HTTPレスポンスに書き込まれます。
テストによるピン止めの検証
pom.xml では、surefire と failafe プラグインに argLine 引数を追加しました:
<argLine>-Djdk.tracePinnedThreads</argLine> <!-- Only works on Java 21-23 -->
-Djdk.tracePinnedThreads は、仮想スレッドをスムーズに アンマウント できない場合(キャリアスレッドをブロックすることを意味する)、スタックトレースをダンプします。
これが pinning と呼ばれるものです(詳細は 仮想スレッドリファレンスガイド を参照してください)。
This flag only works on Java 21-23 and has been removed in Java 24+. For Java 24+, use the Quarkus junit-virtual-threads extension described in the virtual thread reference guide, which uses JFR events to detect pinning across all Java versions.
|
Starting with Java 24, thanks to JEP 491, synchronized blocks no longer cause pinning.
This means pinning is much less likely to occur on Java 24+, occurring only when native code calls back to Java code that performs blocking operations.
|
We recommend enabling pinning detection in tests. Thus, you can check that your application behaves correctly when using virtual threads. Just check your log after having run the test. If you see a stack trace (on Java 21-23) or pinning events (using the junit-virtual-threads extension), better check what’s wrong. If your code (or one of your dependencies) pins, it might be better to use regular worker threads instead.
以下の内容で src/test/java/org/acme/TheBestPlaceToBeResourceTest.java クラスを作成します:
package org.acme;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;
@QuarkusTest
class TheBestPlaceToBeResourceTest {
@Test
void verify() {
RestAssured.get("/")
.then()
.statusCode(200);
}
}
これは単純なテストですが、少なくともアプリケーションがピン止めされているかどうかを検出します。どちらかを使ってテストを実行してください:
-
開発モードで
r(継続的テストを使用) -
./mvnw test
ご覧のとおり、ピン留めは行われず、スタック トレースも行われません。 これは、REST クライアントが仮想スレッドに適した方法で実装されているためです。
結合テストでも同じアプローチが使えます。
まとめ
このガイドでは、Quarkus RESTとRESTクライアントで仮想スレッドを使用する方法を説明しました。 仮想スレッドのサポートについては、こちらを参照してください:
-
メッセージングアプリケーションにおける @RunOnVirtualThread (このガイドでは Apache Kafka を扱います)
-
仮想スレッドリファレンスガイド (ネイティブコンパイルとコンテナ化を含む)