モジュール java.base
パッケージ java.lang

クラスScopedValue<T>

java.lang.Object
java.lang.ScopedValue<T>
型パラメータ:
T - 値の型

public final class ScopedValue<T> extends Object
ScopedValueは、JavaプラットフォームのプレビューAPIです。
プレビュー機能が有効な場合のみ、プログラムでScopedValueを使用できます。
プレビュー機能は、今後のリリースで削除するか、Javaプラットフォームの永続機能にアップグレードすることができます。
メソッド・パラメータを使用せずに、メソッドに安全かつ効率的に共有できる値。

Javaプログラミング言語では、データは通常、メソッド・パラメータを使用してメソッドに渡されます。 データを使用するメソッドを取得するには、様々なメソッドのシーケンスでデータを渡す必要があります。 呼出しのシーケンス内のすべてのメソッドがパラメータを宣言する必要があり、すべてのメソッドがデータにアクセスできます。 ScopedValueは、メソッド・パラメータを使用せずに遠隔地メソッド(通常、「コールバック」)にデータを渡す手段を提供します。 実際には、ScopedValue「暗黙的なメソッド・パラメータ」です。 "かのように"は、一連の呼出しのすべてのメソッドに追加パラメータを持ちます。 どのメソッドもパラメータを宣言せず、ScopedValueオブジェクトにアクセスできるメソッドのみがその値(データ)にアクセスできます。 ScopedValueを使用すると、データのパラメータを宣言せず、データにアクセスできない一連の中間メソッドを介して、「発信者」から遠隔地「逃す」にデータを安全に渡すことができます。

ScopedValue APIは、ScopedValueオブジェクトboundを持つメソッドを、メソッドの限界実行期間の値に対して実行することで機能します。 このメソッドは別のメソッドを呼び出すことができ、別のメソッドを呼び出すことができます。 メソッドの展開実行では、「動的スコープ」を定義します。 ScopedValueオブジェクトにアクセスできるこれらのメソッドのコードは、その値を読み取ることができます。 元のメソッドが正常に完了した場合、または例外が発生した場合、ScopedValueオブジェクトは「バインドなし」に戻ります。 ScopedValue APIでは、値にバインドされたScopedValueを使用して、Runnable.runCallable.callまたはSupplier.getメソッドの実行がサポートされます。

runメソッドを実行するために、値"duke"にバインドされたスコープ値"NAME"を持つ次の例について考えてみます。 runメソッドは、次にdoSomethingを呼び出します。

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

    ScopedValue.runWhere(NAME, "duke", () -> doSomething());
doSomethingによって直接または間接的に実行され、フィールドNAMEにアクセスするコードは、NAME.get()を呼び出して値"duke"を読み取ることができます。 NAME は、runメソッドの実行中にバインドされます。 runメソッドが完了すると、バインド解除に戻ります。

runWhereを使用する例では、結果を返さないメソッドを呼び出します。 callWhereおよびgetWhereを使用して、結果を返すメソッドを起動できます。 また、ScopedValueは、すべてのScopedValueが値にバインドされたメソッドをコールする前に、複数のマッピング(値へのScopedValue)が累積される場合にwhere(ScopedValue, Object)メソッドを定義します。

バインディングはスレッド単位

値へのScopedValueバインディングはスレッド単位です。 xxxWhereを呼び出すと、現在のスレッドの値にバインドされたScopedValueを使用してメソッドが実行されます。 getメソッドは、現在のスレッドにバインドされた値を返します。

この例では、1つのスレッドによって実行されるコードが次を呼び出します:

    ScopedValue.runWhere(NAME, "duke1", () -> doSomething());
そして、別のスレッドによって実行されたコードは次を呼び出します:
    ScopedValue.runWhere(NAME, "duke2", () -> doSomething());
NAME.get()を起動するdoSomething (またはコールするメソッド)のコードは、実行中のスレッドに応じて、値"duke1"または"duke2"を読み取ります。

機能としてのスコープ値

ScopedValueオブジェクトは、ScopedValueがバインドされるときに、その値にアクセスするためのcapabilityまたはキーとして扱う必要があります。 セキュアな使用方法は、アクセス制御(「Java Virtual Machine仕様」、セクション5.4.4を参照してください)に依存し、ScopedValueオブジェクトを共有しないように注意してください。 多くの場合、 ScopedValueは、単一のクラス(またはネスト)のコードのみにアクセスできるように、finalおよびstaticフィールドで宣言されます。

Rebinding

ScopedValue APIを使用すると、「ネストされた動的スコープ」に新しいバインディングを確立できます。 これは「再バインド」と呼ばれます。 値にバインドされたScopedValueは、新しいメソッドの制限付き実行の新しい値にバインドできます。 そのメソッドによって実行されるコードの展開実行によって、ネストされた動的スコープが定義されます。 メソッドが完了すると、ScopedValueの値は以前の値に戻ります。

前述の例では、doSomethingによって実行されるコードが、次を使用してNAMEを新しい値にバインドするとします:

    ScopedValue.runWhere(NAME, "duchess", () -> doMore());
NAME.get()を起動するdoMore()によって直接または間接的に実行されるコードは、値"duchess"を読み取ります。 doMore()が完了すると、NAMEの値は"duke"に戻ります。

Inheritance

ScopedValueは、スレッド間での共有をサポートします。 この共有は、親スレッドによる制限された実行期間内に子スレッドが開始および終了する構造化されたケースに制限されます。 StructuredTaskScopePREVIEWを使用する場合、スコープ値バインディングは、StructuredTaskScopeの作成時に「取得済」となり、forkPREVIEWメソッドを使用してそのタスク・スコープで開始されたすべてのスレッドによって継承されます。

スレッド間で共有されるScopedValueでは、値が不変オブジェクトであるか、または値へのすべてのアクセスが適切に同期される必要があります。

次の例では、ScopedValue NAMEは、実行可能操作の実行のために値"duke"にバインドされます。 runメソッドのコードは、3つのタスクをフォークするStructuredTaskScopeを作成します。 childTask1()childTask2()およびNAME.get()を起動するchildTask3()を実行しているこれらのスレッドによって直接または間接的に実行されるコードは、値"duke"を読み取ります。

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

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

            scope.fork(() -> childTask1());
            scope.fork(() -> childTask2());
            scope.fork(() -> childTask3());

            ...
         }
    });

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

APIのノート:
ScopedValueは、メソッド・パラメータを使用せずにデータの"一方向伝送"が目標である場合、ThreadLocalより優先される必要があります。 ThreadLocalは、メソッド・パラメータを使用せずにメソッドにデータを渡すために使用できますが、次のような多くの問題が発生します:
  1. ThreadLocalは、遠距離呼び出し先のコードが新しい値をsettingから防ぐことはありません。
  2. ThreadLocalにはバインドされていない存続期間があるため、明示的にremovedを指定しないかぎり、メソッドの完了後も引き続き値があります。
  3. 「継承」は高価 - 各子スレッドを作成するときに、スレッドと値のマップをコピーする必要があります。
実装上のノート:
スコープ値は、かなり小さい数値で使用するように設計されています。get()は最初にスコープを囲んで検索を実行し、スコープ値の最も内側のバインディングを見つけます。 次に、検索の結果が小さいスレッド・ローカル・キャッシュにキャッシュされます。 そのスコープ値に対するget()の以降の呼出しは、ほとんどの場合非常に高速です。 ただし、プログラムに周期的に使用するスコープ値が多数ある場合、キャッシュ・ヒット率は低くなり、パフォーマンスは低下します。 この設計により、StructuredTaskScopePREVIEWスレッドによるスコープ指定値の継承が非常に高速になります: 本質的には、ポインタをコピーし、スコープ値バインディングを残すだけでも、ポインタを更新するよりはほとんど必要ありません。

スレッドごとのスコープ値キャッシュは小さいため、クライアントは使用中のバインドされたスコープ値の数を最小限に抑える必要があります。 たとえば、この方法で多数の値を渡す必要がある場合、これらの値を保持するレコード・クラスを作成し、そのレコードのインスタンスに単一のScopedValueをバインドすることが理にかなっています。

このリリースのリファレンス実装には、スコープ値のパフォーマンスを調整するためのいくつかのシステム・プロパティが用意されています。

システム・プロパティjava.lang.ScopedValue.cacheSizeは、(per-thread)スコープ値キャッシュのサイズを制御します。 このキャッシュは、スコープ値のパフォーマンスに不可欠です。 小さすぎる場合、ランタイム・ライブラリはget()ごとに繰り返しスキャンする必要があります。 大きすぎる場合、メモリーは不必要に消費されます。 デフォルトのスコープ値キャッシュ・サイズは16エントリです。 サイズは2 ~ 16エントリに変更できます。 ScopedValue.cacheSizeは2の整数累乗である必要があります。

たとえば、-Djava.lang.ScopedValue.cacheSize=8を使用できます。

その他のシステム・プロパティはjdk.preserveScopedValueCacheです。 このプロパティは、仮想スレッドがブロックされたときにスレッドごとのスコープ値キャッシュが保持されるかどうかを決定します。 デフォルトでは、このプロパティはtrueに設定されています。つまり、すべての仮想スレッドがブロックされたときにスコープ値キャッシュを保持します。 ScopedValue.cacheSizeと同様、これはスペースと速度のトレードオフです: 多くの仮想スレッドがほとんどブロックされている場合、このプロパティをfalseに設定するとメモリーを節約できますが、各仮想スレッドのスコープ値キャッシュはブロック操作後に再生成する必要があります。

導入されたバージョン:
21