set-with-a-map スレッドセーフを利用するクラスを作成しようとしています。what
特に同期する必要があるかどうかはわかりません。
マップは に似たものとして定義されますMap<Class<K>, Set<V>> map;
。以下は、マップが実装で内部的に使用されている方法の縮小です。
public void addObject(K key, V object) {
getSet(key).add(object);
}
public void removeObject(K key, V object) {
getSet(key).remove(object);
}
public void iterateObjectsInternally(K key, Object... params)
{
for (V o : getSet(key)) {
o.doSomething(params);
}
}
private Set<V> getSet(K key) {
if (!map.containsKey(key)) {
map.put(key, new Set<V>());
}
return map.get(key);
}
マップの問題
自体を使用する限り、map
私が目にする唯一の同時実行性の問題はgetSet(K)
、スレッドコンテキストが と の間で切り替わる可能性がある にcontainsKey
ありput
ます。この場合、次のことが発生する可能性があります。
[Thread A] map.containsKey(key) => returns false
[Thread B] map.containsKey(key) => returns false
[Thread B] map.put(key, new Set<V>())
[Thread B] map.get(key).add(object)
[Thread A] map.put(key, new Set<V>()) => Thread A ovewrites Thread B's object [!]
[Thread B] map.get(key).add(object)
今、私は現在HashMap
、この実装に定期的に使用しています。そして、私が正しければ、Collection.synchronizedMap()
orConcurrentHashMap
を使用すると、メソッドレベルでの同時実行の問題のみが解決されます。つまり、メソッドはアトミックに実行されます。これらは、メソッドが互いに相互作用する方法について何も述べていないため、並行ソリューションを使用している場合でも、次のことが発生する可能性があります。
ConcurrentHashMap
ただし、メソッドがありputIfAbsent
ます。これの欠点はmap.putIfAbsent(key, new Set<V>())
、セットが要求されるたびにステートメントが新しいセットを作成することです。これは多くのオーバーヘッドのようです。
一方で、これら 2 つのステートメントを同期ブロックでラップするだけで十分でしょうか?
synchronized(map) {
if (!map.containsKey(key)) {
map.put(key, new Set<V>());
}
}
マップ全体をロックするよりも良い方法はありますか? マップの他の値の読み取りがロックアウトされないように、キーのみをロックする方法はありますか?
synchronized(key) {
if (!map.containsKey(key)) {
map.put(key, new Set<V>());
}
}
キーは必ずしも同じオブジェクトである必要はありませんが (具体的にはClass<?>
型です)、ハッシュコードは等しいことに注意してください。同期にkey
オブジェクト アドレスの同等性が必要な場合、同期は機能しない可能性があります。
セットの問題
より大きな問題は、セットが適切に使用されているかどうかを知ることだと思います. オブジェクトの追加、オブジェクトの削除、オブジェクトの反復など、いくつかの問題があります。
と での同時実行の問題を回避するには、リストをラップするCollections.synchronizedList
だけで十分でしょうか? 同期されたラッパーはそれらをアトミック操作にするので、それで問題ないと思います。addObject
removeObject
ただし、繰り返しは別の話かもしれません。の場合iterateObjectsInternally
、セットが同期されている場合でも、外部で同期する必要があります。
Set<V> set = getSet(key);
synchronized(set) {
for (V value : set) {
// thread-safe iteration
}
}
しかし、これはひどい無駄のように思えます。代わりに、単純にCopyOnWriteArrayList
orCopyOnWriteArraySet
を定義として置き換えたらどうなるでしょうか。反復では単に配列の内容のスナップショットを使用するだけなので、別のスレッドから変更する方法はありません。また、CopyOnWriteArrayList
追加および削除メソッドで再入可能ロックを使用します。これは、追加/削除も本質的に安全であることを意味します (それらは同期されたメソッドであるため)。CopyOnWriteArrayList
内部構造の反復回数がリストの変更回数を大幅に上回っているため、魅力的に見えます。また、コピーされた反復子を使用すると、別のスレッドで( ) からの反復を心配しaddObject
たり、removeObject
台無しにしたりする必要がありません。iterateObjectInternally
ConcurrentModificationExceptions
これらの並行性チェックは正しい軌道に乗っているか、および/または十分に厳密ですか? 私は並行プログラミングの問題を抱えた初心者であり、明らかな何かを見落としているか、考えすぎている可能性があります。同様の質問がいくつかあることは知っていますが、私の実装は、私と同じように具体的に質問することを正当化するのに十分なほど異なっているように見えました。