Programmingバックエンド開発者

Javaにおけるセーフパブリケーションとは何か、マルチスレッドにおいてなぜ必要であり、どのようにそれを確保するのかを説明してください。

Hintsage AIアシスタントで面接を突破

回答

セーフパブリケーションとは、スレッド間でオブジェクトを安全に公開することを保証することです。1つのスレッドがオブジェクトを作成し初期化する場合、別のスレッドは常に完全に構成されたオブジェクトを必ず見ることができ、部分的に初期化された状態を見ることはありません。

セーフパブリケーションなしでは、レースコンディションが発生する可能性があります:1つのスレッドが初期化されていないオブジェクトの部分を目にすることがあるため、たとえコンストラクタがすでに実行されていたとしてもです。

セーフパブリケーションを確保する方法:

  • finalフィールドを使用する - それらの値はコンストラクタの後で他のスレッドに確実に見えます。
  • volatile変数またはAtomicReferenceを通じてオブジェクトを公開する。
  • スレッドセーフなコンテナ(例えば、java.util.concurrentのコレクション)を通じてオブジェクトを公開する。
  • オブジェクトの作成および読み取り時にsynchronizedを使用する。

例(不安全):

public class Holder { private int n; public Holder(int n) { this.n = n; } } Holder holder; void publish() { holder = new Holder(42); } // 安全性は保証されない

完全に構成されていないオブジェクトが見える可能性があります!

例(安全):

volatile Holder holder; void publish() { holder = new Holder(42); } // holderの読み取りも安全です

ひっかけ問題

オブジェクト内のすべてのフィールドがfinalの場合、常にセーフパブリケーションを保証しますか?

回答: いいえ、新しいオブジェクトへの参照がコンストラクタの終了前に公開される場合のみです。参照がコンストラクタの終了前に他のスレッドに渡ると、フィールドは完全に初期化されない可能性があります。コンストラクタの実行中にthisや完全に構成されていないオブジェクトへの参照を公開することは避けるべきです。

例(危険!):

public class PublishEscape { public static PublishEscape instance; public PublishEscape() { instance = this; // 悪い!thisはコンストラクタの終了前に公開される } }

歴史

開発者は、コンストラクタが終了する前に、staticを介してアクセス可能なフィールドにServiceのインスタンスへの参照を割り当てていました。その結果、別のスレッドが部分的に構成されたオブジェクトを取得し、プロダクションで予測できない動作を引き起こしました。


歴史

Webアプリケーションでは、シングルトンオブジェクトが追加の同期なしに作成されていました。負荷の下で、一部のスレッドはnullまたは不正に初期化されたフィールドを取得し、Intermittent NullPointerExceptionを引き起こしました。


歴史

ライブラリでは、スレッド間のキャッシュに通常のListを使用していました - セーフパブリケーションが欠如し、初期化は新しいスレッドに対する可視性を保証しませんでした。その結果、キャッシュは混沌とした状態で動作し、データの整合性が損なわれました。