プロンプトとユースケース
あなたは、コストの高い設定オブジェクトを遅延生成し、成功した結果を一度だけ公開しなければならない高並行性サービスを保守しています。Java 25 の StableValue を使用し、並行性、障害、可観測性、プレビュー機能のロールアウト、およびロールバックの境界について説明してください。このプロンプトでは、Java の並行性、JMM の公開推論、および最新の JDK API に対する判断力がテストされます。
面接官がテストしていること
- StableValue を安定した長期的なコントラクトではなく、Java SE 25 のプレビュー API であると正しく認識しているか。
- 単一書き込みコンテナと不変オブジェクトを区別できているか。
orElseSet、trySet、setOrThrow、およびorElseThrowにおける障害とリトライの動作を説明できるか。- 初期化の例外、競合する初期化処理、シャットダウン、およびアップグレードを適切に処理できるか。
回答前に明確にすべき質問
- 初期化が失敗した後、システムはリトライしてよいのか、それとも最初の失敗でサーキットブレーカーをトリップさせるのか?
- 生成処理に副作用はあるか、また競合する呼び出し元は 1 つの共有結果を待つ必要があるか?
- ターゲットランタイムはプレビュー機能を有効化できるか、またリリース規約でプレビュー API が許可されているか?
- そのオブジェクトは真に不変であるか、あるいは追加のカプセル化やライフサイクル管理が必要か?
30秒の回答フレームワーク
私は StableValue を、内部のオブジェクトに対する自動的なスレッドセーフ保証としてではなく、最大で 1 回だけ正常に設定できるコンテナとして扱います。値を完全に構築および検証した上で orElseSet で公開し、競合するリーダーは公開された値を消費します。障害がリトライ可能な場合は、リトライ制限、例外クラス、および副作用のクリーンアップを定義します。障害が致命的である場合は、起動時検証とともに setOrThrow を使用します。JDK 25 ではこの API がまだプレビュー扱いであるため、ロールアウト計画には有効化フラグ、モニタリング、ロールバック、およびアップグレードテストを含める必要があります。
ステップバイステップの詳細解説
1. StableValue のコントラクト
StableValue は 1 つの non-null 値を保持し、最大 1 回だけ正常に設定されることを許可します。ランタイムが安定した値を認識して安全な公開を最適化できるように、読み取り、試行設定(try-set)、および例外送出設定の各操作を提供します。
2. volatile および AtomicReference との違い
volatile は繰り返しの書き込みを許可し、AtomicReference は更新や compare-and-set をサポートします。StableValue は「正常に設定された後は決して変更されない」ことを直接表現します。これは、1 回限りの設定、パース済み結果、または共有ハンドルに適しており、リフレッシュ可能または置換可能なキャッシュには適していません。
3. 1回限りの遅延初期化コード
import java.lang.StableValue;
final class CatalogHolder {
private final StableValue<Catalog> catalog = StableValue.of();
Catalog get() {
return catalog.orElseSet(this::loadCatalog);
}
private Catalog loadCatalog() {
return Catalog.loadFrom("/etc/catalog.json");
}
}ファクトリは復帰する前にすべての検証を完了させる必要があります。不完全に初期化されたオブジェクトを公開して後から変更するようなことは絶対に避けてください。
4. 競合する初期化の処理
複数のスレッドが orElseSet を呼び出した場合、正常に公開される値は 1 つだけであり、他のスレッドはその値を読み取る必要があります。ファクトリは並行して実行される可能性があるため、複数回実行される可能性があることを前提とし、二重登録、課金、ファイル作成などの不可逆的な副作用を回避してください。
5. 例外とリトライポリシー
ファクトリが例外を送出した場合、値はインストールされず、後続の呼び出しが再試行する可能性があります。一時的な障害と確定的な設定エラーを分離し、バックオフ、試行制限、およびメトリクスを設定します。リトライが禁止されている場合は、起動時の障害をキャッチして setOrThrow を使用するか、意図的にプロセスを終了します。
6. trySet と setOrThrow を使い分けるタイミング
trySet はこの試行が成功したかどうかを報告するため、明示的な競合制御に適しています。setOrThrow は既に設定されている場合や値が無効な場合に例外を送出するため、書き込み側が 1 つであることが保証されている起動パスに適しています。リーダーは初期化がすでに完了していなければならないことを表明するために orElseThrow を使用できます。
7. 公開の可視性とオブジェクトの状態
コンテナは値がいつ可視化されるかを定義しますが、オブジェクトを内部的にスレッドセーフにするわけではありません。final フィールド、不変コレクション、または制御された同期を使用して、構築後に Catalog のフィールドが変更されないようにします。ミュータブルなリソースを保持している場合は、クローズおよび並行アクセスのプロトコルを定義します。
8. プレビュー API ロールアウトのエンジニアリング
JDK 25 では StableValue がプレビューとしてドキュメント化されています。コンパイルと実行には適切なプレビューオプションが必要であり、CI、コンテナイメージ、IDE、本番スクリプト間で一貫性を保つ必要があります。プレビュー API を不可逆な境界に公開する前に、フォールバック実装、互換性マトリクス、およびアップグレードのロールバック手順を記録しておきます。
トレードオフと境界
- 値のリフレッシュ、置換、またはエビクションが必要な場合は、キャッシュまたはアトミック参照を使用してください。StableValue にはそのようなライフサイクル操作はありません。
- ファクトリに外部的な副作用がある場合、競合する呼び出しによってそれらが繰り返される可能性があります。操作をべき等にするか、公開前に別のロックで調整してください。
- 呼び出し元が初期化を待機する必要がある場合は、より上位のレイヤーで Future を使用してください。StableValue がブロッキング調整を提供すると想定しないでください。
- プレビューのパフォーマンスに関する主張には、コールドスタート、競合、例外、および GC を網羅したベンチマークが必要です。
実装計画とエビデンス
- JDK 25 プレビューを有効にして最小限の実装をコンパイルし、
orElseSet、trySet、およびsetOrThrowの戻り値と例外を検証します。 - 並行ストレステストを実行し、ファクトリの呼び出し回数、正常な公開、重複した副作用、および読み取りレイテンシを測定します。
- 構築時の例外とタイムアウトを注入して、リトライ、バックオフ、アラート、およびプロセスの終了動作を検証します。
- ターゲットランタイムにおいて、起動フラグ、コンテナ JDK、モニタリング、およびロールバックスクリプトを検証します。
- Oracle の API、JDK 25 移行ノート、および JVM 仕様をレビューのエビデンスとして使用します。
よくある間違いとフォローアップの質問
間違い 1: StableValue を不変オブジェクトと呼ぶこと
コンテナへの正常な書き込みを制限するだけであり、内部のオブジェクトを凍結するわけではありません。オブジェクト自体の不変性設計を説明してください。
間違い 2: ファクトリが 1 回だけ実行されると想定すること
競合や失敗したリトライによって、複数回実行される可能性があります。べき等な副作用と呼び出し回数の検証について説明してください。
間違い 3: プレビューの有効化を無視すること
JDK 25 上でローカルにコンパイルできるからといって、本番環境への準備が整ったことの証明にはなりません。コンパイル、起動、コンテナ、および CI におけるプレビューフラグを確認してください。
フォローアップ: なぜ volatile の Double-Checked Locking を使い続けないのか?
volatile はリフレッシュ可能な参照に対して依然として適しています。明示的な 1 回限りの公開においては、StableValue のほうが意図をより適切に伝達できますが、プレビューのリスクを受け入れ、あらゆる利点をベンチマークで証明する必要があります。
フォローアップ: 副作用が重複していないことをどのように証明しますか?
ファクトリに可観測なカウンターとべき等性キーを追加し、競合、例外、およびリトライの条件下でカウント、リソースハンドル、および最終的な値の一貫性を検証します。