への参照が安全に公開されている場合に限り、イディオムは安全です。安全な公開は、それ自体の内部に関連するものではなく、構築中のスレッドがマップへの参照を他のスレッドから見えるようにする方法を扱います。HashMap
HashMap
基本的に、ここで可能な唯一の競合は、の構築と、HashMap
それが完全に構築される前にそれにアクセスする可能性のある読み取りスレッドの間の競合です。ほとんどの議論はマップオブジェクトの状態に何が起こるかについてですが、これを変更することはないため、これは重要ではありませんHashMap
。
たとえば、次のようにマップを公開するとします。
class SomeClass {
public static HashMap<Object, Object> MAP;
public synchronized static setMap(HashMap<Object, Object> m) {
MAP = m;
}
}
...そして、ある時点setMap()
でマップで呼び出され、他のスレッドがSomeClass.MAP
マップへのアクセスに使用して、次のようにnullをチェックします。
HashMap<Object,Object> map = SomeClass.MAP;
if (map != null) {
.. use the map
} else {
.. some default behavior
}
おそらく安全であるように見えても、これは安全ではありません。問題は、別のスレッドのセットと後続の読み取りの間に前に発生する関係がないSomeObject.MAP
ため、読み取りスレッドは部分的に構築されたマップを自由に参照できることです。これはほとんど何でもでき、実際には、読み取りスレッドを無限ループに入れるようなことも行います。
安全マップを公開するには、確立する必要がたまたま、前との関係を参照の書き込みにHashMap
(すなわち、出版物)およびその参照のその後の読者(すなわち、消費)。便利なことに、唯一のいくつかの覚えやすい方法があります達成それは、[1] :
- 適切にロックされたフィールドを介して参照を交換する(JLS 17.4.5)
- 静的初期化子を使用してストアを初期化します(JLS 12.4)
- 揮発性フィールド(JLS 17.4.5)を介して、またはこのルールの結果として、AtomicXクラスを介して参照を交換します
- 値を最終フィールドに初期化します(JLS 17.5)。
シナリオで最も興味深いのは、(2)、(3)、(4)です。特に、(3)は上記のコードに直接適用されます。宣言を次のように変換した場合MAP
:
public static volatile HashMap<Object, Object> MAP;
次に、すべてがコーシャです。null 以外の値を参照するリーダーは、ストアと必然的に関係が発生MAP
し、マップの初期化に関連付けられているすべてのストアを参照します。
(2)(静的初期化子を使用)と(4)(finalを使用)はどちらもMAP
実行時に動的に設定できないことを意味するため、他のメソッドはメソッドのセマンティクスを変更します。それを行う必要がない場合は、MAP
として宣言するだけで、static final HashMap<>
安全な公開が保証されます。
実際には、「変更されていないオブジェクト」に安全にアクセスするためのルールは単純です。
(宣言されたすべてのフィールドのように)本質的に不変ではないオブジェクトを公開する場合final
:
- あなたは既に宣言した瞬間に割り当てられることになるオブジェクトを作成することができ、Aを:ちょうど使用
final
(含むフィールドをstatic final
静的メンバのため)。
- 参照がすでに表示されている後、オブジェクトを後で割り当てたい場合は、揮発性フィールドbを使用します。
それでおしまい!
実際には、非常に効率的です。static final
たとえば、フィールドを使用すると、JVMはプログラムの存続期間中は値が変更されていないと想定し、大幅に最適化できます。final
メンバーフィールドを使用すると、ほとんどのアーキテクチャで、通常のフィールド読み取りと同等の方法でフィールドを読み取ることができ、それ以上の最適化が妨げられることはありませんc。
最後に、の使用にvolatile
はいくつかの影響があります。多くのアーキテクチャ(x86など、特に読み取りで読み取りを渡すことができないアーキテクチャ)ではハードウェアバリアは必要ありませんが、コンパイル時に一部の最適化と並べ替えが行われない場合がありますが、これは効果は一般に小さいです。代わりに、実際に要求した以上のものが得られます-安全に公開できるだけでなく、同じ参照に必要なだけHashMap
多くの未変更HashMap
のを保存でき、すべての読者が安全に公開された地図を見ることができます。 。
詳細については、ShipilevまたはMansonとGoetzによるこのFAQを参照してください。
[1] Shipilevから直接引用。
それは複雑に聞こえますが、つまり、宣言時に、またはコンストラクター(メンバーフィールド)または静的初期化子(静的フィールド)のいずれかで、構築時に参照を割り当てることができるということです。
bオプションで、synchronized
メソッドを使用して取得/設定、AtomicReference
または何かを行うことができますが、ここでは、実行できる最低限の作業について説明します。
cメモリモデルが非常に弱い一部のアーキテクチャ(私はあなたを見ている、Alpha)は、final
読み取りの前になんらかの種類の読み取りバリアを必要とする場合がありますが、今日これらは非常にまれです。