ダブルチェックイディオムで遅延ロードされたフィールドをリセットする
-
08-07-2019 - |
質問
「インスタンスフィールドの遅延初期化のダブルチェックイディオム」を検討してください:
// Item 71 in Effective Java copied from this interview with Bloch. private volatile FieldType field; FieldType getField() { FieldType result = field; if (result == null) { // First check (no locking) synchronized(this) { result = field; if (result == null) // Second check (with locking) field = result = computeFieldValue(); } } return result; }
安全な方法でフィールドをリセットできるようにしたい(私の場合、データベースから強制的に再ロードする)。リセットメソッドを使用することでこれができると思います:
void reset() { field = null; }
これはフィールドをリセットする標準的な方法ですか?安全ですか?落とし穴はありますか? Blochがダブルチェックレイジーローディングについて次の警告を出したので、私は尋ねています。コピーして貼り付けてください。通常は良い考えではありませんが、ここでは適切です。"
事前に感謝します、 ヒマラヤのプラヤ。
解決
はい、これはスレッドセーフです。
同期ブロックは、複数のスレッドが不必要に computeFieldValue()
を呼び出すことを防ぐためのものです。 field
は揮発性であるため、 reset
および getField
のアクセスはすべて適切に順序付けられています。
最初のチェックがnullでない場合、 getField
が実行されます。 result
が返されます。
それ以外の場合、フィールドを非ヌルに設定する可能性のある他のスレッドを除き、ロックを取得しますが、どのスレッドでも field
をヌルに設定できます。スレッドが field
をnullに設定した場合、何も変更されていないはずです。それがスレッドを同期ブロックに入れた状態です。現在のスレッドのチェック後に別のスレッドがすでにロックを取得しており、フィールドを非ヌル値に設定している場合、2番目のチェックはそれを検出します。
他のヒント
これは安全なはずだと思いますが、それはフィールドをローカル変数に保存しているからです。これが完了すると、別のスレッドがフィールドの値を途中でリセットしている場合でも、ローカル変数参照を魔法のようにnullに変更する方法はありません。
resetメソッドが上記の reset()
メソッドである限り、これは機能するようです。ただし、 reset()
メソッドが新しいオブジェクト(以下のような)をインスタンス化する場合、意図したものとは異なるものを返す可能性がありますか?
void reset() {
field = new FieldType();
}
スレッドセーフの意味に正確に依存していると思います。
最初のインスタンスが2番目のインスタンスの後に使用される状況になる可能性があります。それは大丈夫かもしれませんし、そうでないかもしれません。
reset()メソッドは正しくないと思います。アイテム71を読むと、次のことがわかります。
このコードは少し複雑に見える場合があります。特に、ローカルの必要性 変数の結果が不明確な場合があります。この変数が行うことは、フィールドが 既に初期化されている一般的なケースでは読み取り専用を1回。
遅延初期化では、フィールドが変更される可能性はありません。これらの演算子の間でフィールドがnullに設定される場合:
FieldType result = field;
if(result == null){//最初のチェック(ロックなし)
getField()は誤った結果を提供します。