S-JIS[2022-09-25/2023-03-22] 変更履歴

仮想スレッド(Java19〜20)

Java19〜20の仮想スレッド(プレビュー版)について。


概要

2022/9/21にリリースされたJava19で、プレビュー版として仮想スレッドが使えるようになった。

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

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

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


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


仮想スレッドはJava21で正式機能になった。[2023-09-23]


仮想スレッドはJava19〜20ではプレビュー版の機能なので、この機能を使いたい場合はコンパイル時にjavacコマンドに--enable-previewを付ける必要があり、
また、実行時にjavaコマンドに--enable-previewを付ける必要がある。
JShellで試す場合もjshellコマンドに--enable-previewを付ける。

> javac --enable-preview --release 19 Example.java
> java --enable-preview Example

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

  仮想スレッド プラットフォームスレッド
 
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以上)。
allowSetThreadLocals(true) ThreadLocal.set()を許可するかどうか。デフォルトはtrue相当(許可する)。
falseにすると、このスレッド内で使われるThreadLocal.set()がUnsupportedOperationExceptionを出す。
(仮想スレッドは大量に作られる想定で、その場合はThreadLocalも非常に大きくなる可能性があるので、使わない方がいいらしい)
正式版(Java21)では、仮想スレッドでもThreadLocalが使えるようになったらしく、allowSetThreadLocals()は無くなった。[2023-09-23]
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 出力ファイル

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