セーフパブリケーションとは、スレッド間でオブジェクトを安全に公開することを保証することです。1つのスレッドがオブジェクトを作成し初期化する場合、別のスレッドは常に完全に構成されたオブジェクトを必ず見ることができ、部分的に初期化された状態を見ることはありません。
セーフパブリケーションなしでは、レースコンディションが発生する可能性があります:1つのスレッドが初期化されていないオブジェクトの部分を目にすることがあるため、たとえコンストラクタがすでに実行されていたとしてもです。
セーフパブリケーションを確保する方法:
例(不安全):
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を使用していました - セーフパブリケーションが欠如し、初期化は新しいスレッドに対する可視性を保証しませんでした。その結果、キャッシュは混沌とした状態で動作し、データの整合性が損なわれました。