私はかなり読みましたが、決定的な答えは見つかりませんでした。
次のようなクラスがあります。
public class Foo() {
private static final HashMap<String, HashMap> sharedData;
private final HashMap myRefOfInnerHashMap;
static {
// time-consuming initialization of sharedData
final HashMap<String, String> innerMap = new HashMap<String, String>;
innerMap.put...
innerMap.put...
...a
sharedData.put(someKey, Java.util.Collections.unmodifiableMap(innerMap));
}
public Foo(String key) {
this.myRefOfInnerHashMap = sharedData.get(key);
}
public void doSomethingUseful() {
// iterate over copy
for (Map.Entry<String, String> entry : this.myRefOfInnerHashMap.entrySet()) {
...
}
}
}
そして、FooのインスタンスからsharedDataにアクセスするのがスレッドセーフかどうか疑問に思っています(コンストラクターとdoSomethingUseful()に示されているように)。 Fooの多くのインスタンスは、マルチスレッド環境で作成されます。
私の意図は、sharedDataが静的初期化子で初期化され、その後は変更されないことです(読み取り専用)。
私が読んだことは、不変オブジェクトは本質的にスレッドセーフであるということです。しかし、これはインスタンス変数のコンテキストのように見えるものでしか見たことがありません。不変の静的変数はスレッドセーフですか?
私が見つけたもう1つの構造はConcurrentHashMapでした。私はタイプsharedCurrentHashMapのsharedDataを作成できますが、それに含まれるHashMapsもタイプConcurrentHashMapである必要がありますか?基本的に..
private static final ConcurrentHashMap<String, HashMap> sharedData;
または
private static final ConcurrentHashMap<String, ConcurrentHashMap> sharedData;
それともより安全ですか(単にclone()するほうがコストがかかります)?
this.myCopyOfData = sharedData.get(key).clone();
TIA。
(静的初期化子は、より多くのコンテキストを提供するために編集されています。)
最終的なsharedData
へのreferenceは変更できないため、スレッドセーフです。マップの内容は[〜#〜] not [〜#〜]スレッドセーフです。これは、できればGuava ImmutableMap
実装またはJava.util.Collections.unmodifiableMap()
でラップする必要があるためですまたは、Java.util.concurrent
パッケージのMap実装の1つを使用します。
[〜#〜] both [〜#〜]を実行した場合にのみ、マップ上で包括的なスレッドセーフが得られます。含まれているマップはすべて、不変であるか、並行実装の1つである必要があります。
デフォルトのクローンは浅いクローンであり、完全なコピーではなくコンテナオブジェクトへの参照を返すだけです。理由については、一般に入手可能な情報に詳しく記載されています。
静的初期化ブロック内の静的最終フィールドの初期化は、スレッドセーフです。ただし、静的最終参照が指すオブジェクトはnotになる可能性があることに注意してください。参照先のオブジェクトがスレッドセーフである(たとえば、それが不変である)場合は、問題はありません。
質問で提案されているようにConcurrentHashMapを使用しない限り、外側のHashMapに含まれる個々のHashMapがスレッドセーフであるとは限りません。スレッドセーフな内部HashMap実装を使用しない場合、2つのスレッドが同じ内部HashMapにアクセスすると、意図しない結果が生じる可能性があります。 ConcurrentHashMapの一部の操作のみが同期されることに注意してください。たとえば、反復はスレッドセーフではありません。
スレッドセーフとは何ですか?もちろん、HashMapの初期化は、すべてのFooが同じMapインスタンスを共有し、静的なinitで例外が発生しない限り、Mapが存在することが保証されているという点で、スレッドセーフです。
しかし、マップのコンテンツを変更することは、スレッドセーフではありません。 static finalは、マップsharedDataを別のマップに切り替えることができないことを意味します。しかし、マップの内容は別の問題です。特定のキーを同時に複数回使用すると、同時実行性の問題が発生する可能性があります。
はい、これもスレッドセーフです。静的クラスのすべての最終メンバーは、スレッドがアクセスできるようになる前に初期化されます。
初期化中にstatic
ブロックが失敗した場合、ExceptionInInitializerError
が最初に初期化を試行するスレッドで発生します。その後クラスを参照しようとすると、NoClassDefFoundError
が発生します。
一般に、HashMap
の内容は、スレッド間の可視性を保証しません。ただし、クラス初期化コードはsynchronized
ブロックを使用して、複数のスレッドがクラスを初期化しないようにします。この同期により、マップ(およびマップに含まれるHashMap
インスタンス)の状態がフラッシュされ、すべてのスレッドで正しく表示されます(マップまたはマップに含まれるマップに変更が加えられていない場合)。クラス初期化子の外。
クラスの初期化と同期の要件については、 Java言語仕様、§12.4.2 を参照してください。
いいえ。それらが不変である場合を除きます。
彼らがする唯一のことは
それでも属性が変更可能な場合は、スレッドセーフではありません。
クラスレベルであることを除いて、まったく同じです。
final static
変数について、本質的にスレッドセーフなものはありません。メンバー変数final static
を宣言すると、この変数が1回だけ割り当てられることが保証されます。
スレッドセーフティの問題は、変数の宣言方法とは関係がありませんが、変数の操作方法に依存しています。したがって、プログラムの詳細なしに質問に答えることは実際には不可能です。
sharedData
変数の状態を変更しますか?sharedData
のすべての書き込み(および読み取り)で同期しますか?ConcurrentHashMapを使用すると、Map
の個々のメソッドがスレッドセーフであることが保証されるだけであり、このようなスレッドセーフな操作は行われません。
if (!map.containsKey("foo")) {
map.put("foo", bar);
}
sharedData
の静的初期化がスレッドセーフであり、1回だけ実行されるかどうかを実際に確認していませんか?
はい、そうです。
もちろん、ここの多くの人々は、sharedData
の内容は依然として変更できることを正しく指摘しています。
この場合、sharedDataオブジェクトのみが不変です。つまり、常に同じオブジェクトで作業することになります。しかし、その中のデータはいつでも、どのスレッドからでも変更(削除、追加など)できます。