導入:なぜ今、スレッドの生成方法を意識すべきなのか
Java 21で正式導入された「仮想スレッド(Virtual Threads)」は、従来のJava開発における並行処理の常識を覆しました。これまでの「1スレッド=1OSスレッド」というモデルでは、数千以上のスレッドを扱うとメモリ不足やコンテキストスイッチのオーバーヘッドが問題となっていました。しかし、仮想スレッドを使えば、軽量なスレッドを数百万個単位で起動可能です。本稿では、Java 21以降の標準である「スレッドファクトリー」を用いたスレッド生成の使い分けについて解説します。
基礎知識:プラットフォームスレッド vs 仮想スレッド
Javaのスレッドには大きく分けて2種類あります。
プラットフォームスレッド(Platform Threads)は、従来のJavaスレッドです。OSのスレッドに1対1で対応しており、生成コストが高く、リソース消費も大きいです。
仮想スレッド(Virtual Threads)は、JVMが管理する軽量なスレッドです。OSスレッドを直接占有せず、ブロッキングが発生するとJVMが自動的にスレッドを切り替えるため、I/O待ちの多い処理を効率化するのに適しています。
実装:Thread.ofVirtual() と Thread.ofPlatform() の使い方
Thread.ofVirtual() と Thread.ofPlatform() を使うことで、これまでのように new Thread() を直接呼び出すのではなく、スレッドの特性を明示的に指定して生成できるようになりました。
サンプルプログラム:スレッド生成の比較コード
以下のコードは、プラットフォームスレッドと仮想スレッドをそれぞれ生成し、実行する例です。
import java.lang.Thread;
public class ThreadExample {
public static void main(String[] args) {
// 1. プラットフォームスレッドの生成
Thread platformThread = Thread.ofPlatform()
.name("my-platform-thread")
.start(() -> System.out.println("プラットフォームスレッドで実行中: " + Thread.currentThread()));
// 2. 仮想スレッドの生成
Thread virtualThread = Thread.ofVirtual()
.name("my-virtual-thread")
.start(() -> System.out.println("仮想スレッドで実行中: " + Thread.currentThread()));
try {
platformThread.join();
virtualThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
応用・注意点:現場で陥りやすい罠
仮想スレッドは万能ではありません。注意すべき点がいくつかあります。
1. CPUバウンドな処理には向かない
仮想スレッドはI/O待ち(データベースアクセスやHTTPリクエストなど)が多い処理で真価を発揮します。数値計算のようなCPU負荷が高い処理では、従来のプラットフォームスレッドやスレッドプールを使用する方がパフォーマンスが出る場合があります。
2. synchronizedブロック内での「スレッド固定」
仮想スレッドを使用する際、synchronizedブロック内で長時間ブロッキングを行うと、その期間中OSスレッドが固定(ピン留め)されてしまい、仮想スレッドの利点が損なわれます。可能であれば synchronized の代わりに ReentrantLock を使用してください。
3. ExecutorServiceとの組み合わせ
基本的にはスレッドを直接 new するのではなく、Executors.newVirtualThreadPerTaskExecutor() を活用し、構造化並行処理(Structured Concurrency)と組み合わせることで、より安全で保守性の高い非同期処理が可能になります。
まずは既存のブロッキング処理を仮想スレッドに置き換えて、アプリケーションの応答性がどう変化するか試してみてください。

コメント