代表的な面接トピック

Java 25 Scoped Values: ThreadLocal とどのように比較するか?

コーディング難しい
Offer.cc 編集チーム公開日 更新日

質問

Java 25 の ScopedValue はどのような問題を解決しますか?ThreadLocal と比較し、スレッドプールや仮想スレッドでの正しい使用法を説明してください。

プロンプトとスコープ

面接官は次のように質問する場合があります:「Java 25 の ScopedValue はどのような問題を解決しますか?ThreadLocal と比較し、スレッドプールや仮想スレッドでの正しい使用法を説明してください。」

JEP 506 により、JDK 25 で Scoped Values が正式機能となりました。これらを使用すると、呼び出し元はレキシカルスコープ内の深い階層の呼び出し先や子スレッドと不変データを共有でき、すべてのメソッドパラメータを介してコンテキストを引き渡す必要性を減らすことができます。この質問はライフタイム、可視性、および並行性の境界をテストするものであり、ScopedValue を単に「新しい ThreadLocal」と呼ぶだけでは回答になりません。

面接官がテストしていること

  • ScopedValue を不変でスコープ制御された暗黙のパラメータとして理解しているかどうか。
  • where(...).run(...) または call(...) のバインディングと復元を説明できるかどうか。
  • 可変な ThreadLocal、プールされたスレッドの再利用、および仮想スレッドの継承との違いを認識しているかどうか。
  • キーのアクセス制御、バインディング数、例外の伝播、およびキャンセルを考慮しているかどうか。
  • 明示的なパラメータまたは ThreadLocal が引き続き適切である時期を理解しているかどうか。

明確化のための質問

  • 共有される値は、リクエスト ID、プリンシパル、テナント、または可変のトランザクション状態ですか?
  • 読み取り専用である必要がありますか?また、子タスクはそれを継承すべきですか?
  • 実行はプラットフォームスレッド、仮想スレッド、構造化並行性、または既存のプール上で行われますか?
  • コードがスコープ外でキーを読み取ったり、Carrier を信頼できないコードに公開したりする可能性はありますか?
  • フレームワークは ThreadLocal のクリーンアップ、ミューテーション、または MDC 統合に依存していますか?

30秒の回答

次のように回答できます:

ScopedValue は、Java 25 の不変でレキシカルスコープを持つコンテキストメカニズムです。呼び出し元は ScopedValue.where(key, value).run(...) または call(...) でバインディングを作成します。これはスコープが終了すると復元され、キーを保持しているコードのみがそれを読み取ることができます。ThreadLocal と比較して、プールされたスレッドのクリーンアップや可変状態のリークを減らし、リクエストコンテキスト、仮想スレッド、構造化並行性に役立ちます。段階的に更新する必要がある ThreadLocal を置き換えるものではなく、値がそのスコープ外にエスケープしてはなりません。ターゲット JDK で継承、例外、キャンセル、およびキーの可視性をテストします。

段階的な推論

バインディングモデルを理解する

値は動的な実行範囲内で読み取り可能ですが、バインディングはコード構造によって明示されます:

java
static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();

ScopedValue.where(REQUEST_ID, "req-42").run(() -> {
    audit("start");
    handleRequest();
});

static void audit(String message) {
    logger.info("{} {}", REQUEST_ID.orElse("missing"), message);
}

audit はリクエスト ID パラメータを必要としませんが、バインディングスコープ内でのみ意味のある値を読み取ります。スコープが終了すると、バインディングが呼び出し元スレッドを汚染することはありません。

ThreadLocal の可変性を比較する

ThreadLocal は各スレッドに可変の値を提供します。プールされたスレッドの再利用にはクリーンアップが必要であり、そうしないと次のリクエストが古いコンテキストを観測する可能性があります。ScopedValue は不変の共有のために設計されており、外部の値を一時的にシャドウし、後で復元するネストされたバインディングを備えています。クリーンアップの責任を軽減しますが、深いコードが繰り返し set しなければならない状態マシンを運ぶものではありません。

子タスクと仮想スレッドの処理

JEP 506 は、仮想スレッドおよび構造化並行性における予測可能な共有コストを目標としています。継承が発生するかどうか、いつキャプチャされるか、およびエグゼキュータがどのように動作するかは、ターゲット JDK と API コントラクトに対して検証する必要があります。通常のスレッドプールからの前提をそのまま当てはめないでください。参照がコンテキスト境界をバイパスできないように、共有オブジェクトは不変のままである必要があります。

キーの設計、例外、および互換性

キーは private static final オブジェクトにするか、制御された API の一部にしてください。グローバルに共有されたキーをセキュリティ境界として扱わないでください。欠落している値には orElse または明示的な例外を使用します。Java 25 では、orElse は null を受け入れなくなりました。run または call から抜ける例外は、依然としてスコープを終了し、外部バインディングを復元します。古い JDK の場合は、テスト済みの互換性パスの背後に明示的なパラメータまたは ThreadLocal 実装を維持してください。

質の高い模範解答

私は ScopedValue を、ThreadLocal の可変な代替品ではなく、不変の暗黙のパラメータとして扱います。リクエストのエントリポイントは where(key, value).run または call を使用して構造化スコープを作成します。深い階層のメソッドはキーを介してリクエスト ID、テナント、またはプリンシパルを読み取り、バインディングは終了時に復元されます。これは仮想スレッドや構造化並行性における読み取り専用コンテキストに適しており、プールされたスレッドのクリーンアップ漏れを防ぎます。ThreadLocal は、finally でクリーンアップを行う、段階的に更新する必要がある状態に対して引き続き有用です。キーの可視性を制限し、子タスクの継承、例外、キャンセル、ネストされたシャドウイング、スコープ外の読み取りをテストし、古い JDK 向けに明示的または互換性のある実装を維持します。

よくある間違い

  • ScopedValue を自動クリーンアップ機能を備えた可変の ThreadLocal と呼ぶこと。
  • Carrier や値をスコープ外に保存したり、ライフタイム後にキーを読み取ったりすること。
  • プールされたスレッドの再利用、子タスクの継承、および仮想スレッドの違いを無視すること。
  • コンテキスト全体が不変であると主張しながら、可変オブジェクトを格納すること。
  • 任意のモジュール間でキーを共有し、アクセス制御を暗号化と混同すること。
  • Java 25 の null 不可 orElse ルールを忘れたり、古い JDK 用のパスを省略したりすること。

フォローアップの質問と回答

1. ScopedValue はすべての ThreadLocal を置き換えることができますか?

いいえ。読み取り専用でスコープにバインドされたコンテキストに適しています。コールチェーンを通じて更新する必要がある状態は、必要なクリーンアップ責任を伴いながら、引き続き ThreadLocal または明示的なパラメータを使用できます。

2. ネストされたバインディングでは何が起こりますか?

内部スコープは同じキーに対して一時的に新しい値をバインドできます。スコープを終了すると外部の値が復元されます。例外パスもスコープを抜けるため、内部の値が外部に漏れることはありません。

3. 並行継承をどのようにテストしますか?

プラットフォームスレッド、仮想スレッド、構造化タスク、およびプールされたスレッドの再利用をカバーします。子タスクから見える値、キャンセル後のクリーンアップ、ネストされたシャドウイング、およびスコープ外の読み取りを確認します。テストマトリクス内で JDK バージョンとエグゼキュータ設定を固定します。

公開情報ソース

関連する質問

関連面接ツール

コーディング問題にはスクリーンショットを使用

問題をキャプチャし、制約条件、解法アプローチ、コード、エッジケース、計算量の順に進めます。

ツールを見る