モジュール java.base
パッケージ java.util.concurrent

クラスStructuredTaskScope<T>

java.lang.Object
java.util.concurrent.StructuredTaskScope<T>
型パラメータ:
T - タスク・スコープで実行されるタスクの結果タイプ
すべての実装されたインタフェース:
AutoCloseable
直系の既知のサブクラス:
StructuredTaskScope.ShutdownOnFailurePREVIEW, StructuredTaskScope.ShutdownOnSuccessPREVIEW

public class StructuredTaskScope<T> extends Object implements AutoCloseable
StructuredTaskScopeは、JavaプラットフォームのプレビューAPIです。
プレビュー機能が有効な場合のみ、プログラムでStructuredTaskScopeを使用できます。
プレビュー機能は、今後のリリースで削除するか、Javaプラットフォームの永続機能にアップグレードすることができます。
「構造化並行性」の基本API。 StructuredTaskScopeでは、タスクが複数の同時サブタスクに分割され、メイン・タスクが続行される前にサブタスクが完了する必要があるケースがサポートされます。 StructuredTaskScopeを使用すると、構造化プログラミングの順次演算と同様に、同時演算の存続期間が「構文ブロック」によって制限されるようにできます。

基本操作

StructuredTaskScopeは、そのパブリック・コンストラクタのいずれかを使用して作成されます。 サブタスクを実行するスレッドを開始するforkメソッド、すべてのサブタスクが終了するのを待機するjoinメソッド、およびタスク・スコープをクローズするcloseメソッドを定義します。 APIは、 try-with-resources文で使用することを目的としています。 この意図は、try blockのコードがforkメソッドを使用してスレッドをフォークしてサブタスクを実行し、joinメソッドでサブタスクが終了するまで待機し、「結果を処理」を使用することです。 forkメソッドをコールすると、「フォークされたサブタスク」を表すSubtaskPREVIEWが返されます。 joinがコールされると、Subtaskを使用して、結果が正常に完了したり、サブタスクが失敗した場合は例外を取得できます。
    Callable<String> task1 = ...
    Callable<Integer> task2 = ...

    try (var scope = new StructuredTaskScope<Object>()) {

        Subtask<String> subtask1 = scope.fork(task1);
        Subtask<Integer> subtask2 = scope.fork(task2);

        scope.join();

        ... process results/exceptions ...

    } // close

次の例では、同種のサブタスクのコレクションをフォークし、すべてのサブタスクがjoinメソッドで完了するのを待機し、Subtask.StatePREVIEWを使用して、正常に完了したサブタスクのセットと、失敗したサブタスクの別のサブタスクにサブタスクをパーティション化します。

    List<Callable<String>> callables = ...

    try (var scope = new StructuredTaskScope<String>()) {

        List<Subtask<String>> subtasks = callables.stream().map(scope::fork).toList();

        scope.join();

        Map<Boolean, Set<Subtask<String>>> map = subtasks.stream()
                .collect(Collectors.partitioningBy(h -> h.state() == Subtask.State.SUCCESS,
                                                   Collectors.toSet()));

    } // close

正しい使用を確実にするために、joinおよびcloseメソッドはowner (タスク・スコープを開いたり作成したスレッド)によってのみ起動でき、所有者がフォーク後にjoinメソッドを呼び出さなかった場合、closeメソッドはクローズ後に例外をスローします。

StructuredTaskScopeは、タスク・スコープを閉じずに停止するshutdownメソッドを定義します。 shutdown()メソッド「取消」は、すべての未完了サブタスクを「割り込み」によってスレッドに指定します。 新しいスレッドがタスク・スコープで開始されないようにします。 所有者がjoinメソッドで待機している場合は、ウェイクアップします。

シャットダウンはshort-circuitingに使用され、すべてのサブタスクの完了を必要としない「政策」をサブクラスに実装できます。

一般的なケースのポリシーを持つサブクラス

StructuredTaskScopeの2つのサブクラスは、一般的な場合にポリシーを実装するために定義されています:
  1. ShutdownOnSuccessPREVIEWは、正常に完了する最初のサブタスクの結果を取得します。 取得すると、タスク・スコープがシャットダウンされ、未完了のスレッドが中断され、所有者がウェイクアップされます。 このクラスは、サブタスクの結果が("任意の起動")を実行し、他の未完了サブタスクの結果を待機する必要がない場合に対象となります。 最初の結果を取得するメソッド、またはすべてのサブタスクが失敗した場合に例外をスローするメソッドを定義します。
  2. ShutdownOnFailurePREVIEWは、失敗する最初のサブタスクの例外を取得します。 取得すると、タスク・スコープがシャットダウンされ、未完了のスレッドが中断され、所有者がウェイクアップされます。 このクラスは、すべてのサブタスクの結果が("すべて起動")を必要とする場合を対象としています。サブタスクが失敗した場合、他の未完了サブタスクの結果は不要になります。 サブタスクのいずれかが失敗した場合に例外をスローするメソッドを定義する場合。

次に、2つのクラスを使用する2つの例を示します。 どちらの場合も、2つのURLのロケーション"left"および"right"からリソースをフェッチするために、サブタスクのペアがフォークされます。 最初の例では、最初のサブタスクが正常に完了する結果を取得するShutdownOnSuccessオブジェクトを作成し、タスク・スコープを停止することによってもう一方を取消しします。 サブタスクのいずれかが結果で完了するか、両方のサブタスクが失敗するまで、メイン・タスクはjoinで待機します。 result(Function)PREVIEWメソッドを起動して、取得された結果を取得します。 両方のサブタスクが失敗した場合、このメソッドは、原因としていずれかのサブタスクから例外とともにWebApplicationExceptionをスローします。

    try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {

        scope.fork(() -> fetch(left));
        scope.fork(() -> fetch(right));

        scope.join();

        String result = scope.result(e -> new WebApplicationException(e));

        ...
    }
2番目の例では、失敗する最初のサブタスクの例外を取得するShutdownOnFailureオブジェクトを作成し、タスク範囲を停止してもう一方のサブタスクを取り消します。 メイン・タスクは、両方のサブタスクが完了するまで(失敗するか、期限に達するまで)、joinUntil(Instant)で待機します。 throwIfFailed(Function)PREVIEWを起動して、いずれかのサブタスクが失敗した場合に例外をスローします。 両方のサブタスクが正常に完了した場合、このメソッドはno-opです。 この例では、Supplier.get()を使用して各サブタスクの結果を取得します。 一般的に、forkによって返されるオブジェクトが、正常に完了したサブタスクの結果を取得するためにのみ使用される場合には、SubtaskのかわりにSupplierを使用することをお薦めします。
   Instant deadline = ...

   try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {

        Supplier<String> supplier1 = scope.fork(() -> query(left));
        Supplier<String> supplier2 = scope.fork(() -> query(right));

        scope.joinUntil(deadline);

        scope.throwIfFailed(e -> new WebApplicationException(e));

        // both subtasks completed successfully
        String result = Stream.of(supplier1, supplier2)
                .map(Supplier::get)
                .collect(Collectors.joining(", ", "{ ", " }"));

        ...
    }

StructuredTaskScopeの拡張

StructuredTaskScopeを拡張し、handleCompleteメソッドをオーバーライドして、ShutdownOnSuccessおよびShutdownOnFailureによって実装されたポリシー以外のポリシーを実装できます。 たとえば、サブクラスは、正常に完了したサブタスクの結果を収集し、失敗したサブタスクを無視する場合があります。 サブタスクが失敗した場合に例外が収集される場合があります。 shutdownメソッドを起動して停止し、一部の条件が発生したときにjoinがウェイクアップする場合があります。

サブクラスは、通常、joinメソッドの後に実行されるコードに対して使用可能な結果、状態またはその他の結果を作成するメソッドを定義します。 結果を収集し、失敗したサブタスクを無視するサブクラスでは、結果を返すメソッドを定義できます。 サブタスクが失敗したときに停止するポリシーを実装するサブクラスは、失敗する最初のサブタスクの例外を取得するメソッドを定義できます。

次に、正常に完了した同種のサブタスクを収集する単純なStructuredTaskScope実装の例を示します。 メイン・タスクが結合後に起動できるメソッド"completedSuccessfully()"を定義します。

    class CollectingScope<T> extends StructuredTaskScope<T> {
        private final Queue<Subtask<? extends T>> subtasks = new LinkedTransferQueue<>();

        @Override
        protected void handleComplete(Subtask<? extends T> subtask) {
            if (subtask.state() == Subtask.State.SUCCESS) {
                subtasks.add(subtask);
            }
        }

        @Override
        public CollectingScope<T> join() throws InterruptedException {
            super.join();
            return this;
        }

        public Stream<Subtask<? extends T>> completedSuccessfully() {
            super.ensureOwnerAndJoined();
            return subtasks.stream();
        }
    }

この例のcompletedSuccessfully()メソッドの実装では、ensureOwnerAndJoined()を起動して、メソッドが結合された後にのみ所有者スレッドによってのみ呼び出されるようにします。

ツリー構造

タスク・スコープは、新しいタスク・スコープを開いたときに親子関係が暗黙的に確立されるツリーを形成します:
  • 親子関係は、タスク・スコープで開始されたスレッドが独自のタスク・スコープを開くと確立されます。 タスク・スコープ"A"で開始されたスレッドで、タスク・スコープ"B"を開くと、タスク・スコープ"A"がタスク・スコープ"B"の親である親子関係が確立されます。
  • 親子関係はネストによって確立されます。 スレッドがタスク・スコープ"B"を開き、タスク・スコープ"C" ("B"をクローズする前に)を開くと、包含タスク・スコープ"B"はネストされたタスク・スコープ"C"の親になります。
タスク・スコープのdescendantsは、親である子タスク・スコープと、再帰的に子タスク・スコープの子孫です。

ツリー構造は次のものをサポートします:

  • スレッド間での「スコープ値」PREVIEWの継承。
  • 確認チェック。 メソッド記述の"タスク範囲に含まれるスレッド"というフレーズは、タスク・スコープまたは子孫スコープで開始されたスレッドを意味します。

次の例は、スコープ値の継承を示しています。 スコープ値USERNAMEは、値"duke"にバインドされます。 StructuredTaskScopeが作成され、 childTaskを実行するスレッドを起動するためにそのforkメソッドが起動されます。 スレッドは、タスク・スコープの作成時に取得されたスコープ値「バインド」を継承します。 childTaskのコードは、スコープ値の値を使用するため、値"duke"を読み取ります。

    private static final ScopedValue<String> USERNAME = ScopedValue.newInstance();

    ScopedValue.runWhere(USERNAME, "duke", () -> {
        try (var scope = new StructuredTaskScope<String>()) {

            scope.fork(() -> childTask());
            ...
         }
    });

    ...

    String childTask() {
        String name = USERNAME.get();   // "duke"
        ...
    }

StructuredTaskScopeでは、現時点ではツリー構造を公開するAPIは定義されません。

特に指定しないかぎり、このクラスのコンストラクタまたはメソッドにnull引数を渡すと、NullPointerExceptionがスローされます。

メモリーの一貫性の影響

サブタスクhappen-before「フォーク」より前のタスク・スコープの所有者スレッドまたは含まれるスレッドでのアクション。このサブタスクによって実行されるアクションは、happen-beforeで、サブタスクの結果は「取得済」PREVIEWまたはhappen-beforeで、タスク・スコープのjoiningの後にスレッドで実行されるアクションです。

Java言語仕様を参照してください:
17.4.5 Happens-beforeオーダー
導入されたバージョン:
21