S-JIS[2023-09-23] 変更履歴

仮想スレッド

Javaの仮想スレッドについて。


概要

Java21(プレビュー版ではJava19)で、仮想スレッド(virtual thread)が使えるようになった。

仮想スレッドは、Javaの軽量スレッドである。

仮想スレッドに対し、従来のスレッドのことを「プラットフォームスレッド」と呼ぶ。
プラットフォームスレッドは、OSのスレッドに対応していた(OSのスレッドのラッパーのような実装だった)。
OSスレッドの切り替えにはコストがかかるので、大量のスレッドには向いていない。

仮想スレッドは、プラットフォームスレッド上で仮想スレッドを切り替えて(マウントして)リソースを上手く使ってくれるらしい。
(仮想スレッドの実行時間はプラットフォームスレッドより高速というわけではないので(CPUをフルに使う処理なら同じだろう)、待ちが多くスレッドの切り替え頻度が高い処理に有効らしい)


仮想スレッドも従来のThreadクラスを使用する。
ただし、インスタンスの生成方法が異なる。
(従来のThread(プラットフォームスレッド)のインスタンスの生成方法も、これに合わせて変わる)


仮想スレッドを生成する例。

  仮想スレッド プラットフォームスレッド
 
Runnable task = () -> {
  System.out.println("test");
};
従来の方法  
var thread = new Thread(task);
thread.start(); // スレッド実行開始
thread.join();  // スレッド終了待ち
新しい方法
var thread = Thread.ofVirtual().unstarted(task);
thread.start();
thread.join();
var thread = Thread.ofPlatform().unstarted(task);
thread.start();
thread.join();
var thread = Thread.ofVirtual().start(task);
thread.join();
var thread = Thread.ofPlatform().start(task);
thread.join();
var thread = Thread.startVirtualThread(task);
thread.join();
 
var factory = Thread.ofVirtual().factory();
var thread = factory.newThread(task);
thread.start();
thread.join();
var factory = Thread.ofPlatform().factory();
var thread = factory.newThread(task);
thread.start();
thread.join();

Thread.ofVirtual()やThread.ofPlatform()は、Thread.Builderインスタンスを返す。

ビルダーにはいくつか設定できる項目がある。

var builder = Thread.ofVirtual() // or Thread.ofPlatform()
                    .name("thread-name");
var thread = builder.start(task);
分類 備考
共通 name("name") スレッド名。
thread.getName()で取得される。
name("name", 0) このメソッドでスレッド名を付けると、同じビルダーから複数のスレッドを生成する場合、スレッド名の末尾に連番が付く。
第2引数は連番の初期値(0以上)。
inheritInheritableThreadLocals(true)  
uncaughtExceptionHandler((t, e) -> {
  System.err.println("thread=" + t);
  e.printStackTrace();
})
例外ハンドラー。
プラットフォーム group(threadGroup) (ThreadGroupはほとんど使われておらず、非推奨らしい。@Deprecatedは付いてないけど)
daemon(false) (仮想スレッドは常にデーモンスレッドらしい)
priority(Thread.MAX_PRIORITY) (仮想スレッドの優先度は常にThread.NORM_PRIORITYらしい)
stackSize(n)  

Threadクラスに追加されたメソッド。

    // 仮想スレッドかどうか
    boolean b = thead.isVirtual();

ExecutorServiceの例

ExecutorServiceで仮想スレッドを使う場合は、Executors.newVirtualThreadPerTaskExecutor()を使う。

import java.util.concurrent.Executors;
        Runnable task = () -> {
            System.out.println("test");
        };

        try (var service = Executors.newVirtualThreadPerTaskExecutor()) {
            var future = service.submit(task); // 実行開始
            future.get(); // 終了待ち
        }

ThreadFactoryを使う方法もある。

        var factory = Thread.ofVirtual().factory();
        try (var service = Executors.newThreadPerTaskExecutor(factory)) {
            〜
        }

従来のスレッド(プラットフォームスレッド)はOSのスレッドなので限りがあり、そのためスレッドをプールするのが有効だったらしい。
(Executors.newFixedThreadPool()ではプールするスレッド数を指定する)

しかし仮想スレッドは軽量なのでプールする必要は無いらしい。
(そもそも仮想スレッドは存在期間が短い処理を想定しており、プールしてはいけないらしい)


jcmdの例

仮想スレッドは大量に生成することが出来るので、それをスレッドダンプすると大量になってしまう。
このため、jcmdではjson形式でスレッドダンプを出力できるようになった。

$JAVA_HOME/bin/jcmd プロセスID Thread.dump_to_file -format=json 出力ファイル

Threadへ戻る / Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま