concurrenthashmap putifabsent:get()callが続く場合の原子性
-
25-10-2019 - |
質問
私の論理をチェックするために、同時マップの特定の使用について話し合いたいと思いました...
使用した場合 ConcurrentHashMap
, 、私は家族をすることができます
private final ConcurrentHashMap<K, V> map = new ConcurrentHashMap<K, V>();
public V getExampleOne(K key) {
map.putIfAbsent(key, new Object());
return map.get(key);
}
しかし、私はそれを認識しています 人種状態 の間のマップからアイテムを削除すると存在します putIfAbsent
そしてその get
, 、上記の方法は、コレクションに存在しなくなったものを返します。これは大丈夫かもしれないし、そうでないかもしれないが、私のユースケースでは大丈夫ではないと仮定することができます。
私が本当に望んでいるのは、すべてをアトミックにすることです。そう、
public V getExampleTwo(K key) {
return map.putIfAbsent(key, new Object());
}
しかし、これが拡大するにつれて
if (!map.containsKey(key))
return map.put(key, value); [1]
return map.get(key);
線[1]が返されます null
最初の使用法(つまり、 map.put
初めて使用する以前の値を返します null
).
この場合、nullを返すことはできません
それは私にのようなものを残します。
public V getExampleThree(K key) {
Object object = new Object();
V value = locks.putIfAbsent(key, object);
if (value == null)
return object;
return value;
}
だから、最後に、私 質問;上記の例はセマンティクスでどのように異なりますか?します getExampleThree
のような原子性を確保します getExampleTwo
しかし、null戻りは正しく避けていますか? 他の問題はありますか getExampleThree
?
私は選択肢について少し議論することを望んでいました。私は非を使うことができることに気づきました ConcurrentHashMap
そして、私を呼ぶクライアントを中心に同期します get
マップから削除する方法と方法ですが、それは同時ハップの目的(非ブロックの性質)を打ち負かすようです。 それは、データを保持するための私の唯一の選択です 正確?
それが、Concurrenthashmapを選択する理由のほんの一部だと思います。目に見える/最新の/激しい/エカレートあなたがそれとやり取りするポイントで、しかし、古いデータが問題になる場合、さらに影響があるかもしれません...
解決
キーのグローバルロックオブジェクトを作成しようとしているようです。
エントリがほとんどすぐに再作成される可能性でエントリを削除する代わりに、もう一度必要ではないと確信したときにのみエントリを削除します。
それ以外の場合、これをロックに使用している場合、同じキーの異なるオブジェクトに2つのスレッドロックを使用できます。
問題なくなった場合は、ループに忙しくなります。
public V getExampleOne(K key) {
for(Object o = null, ret = null; (ret = map.get(key)) == null; )
map.putIfAbsent(key, o == null ? o = new Object() : o);
return ret;
}
ループが存在するとすぐに削除または交換することができます。
public V getExampleThree(K key) {
Object o = new Object();
map.putIfAbsent(key, o);
Object ret = map.get(key);
return ret == null ? o : ret;
}
だから、最後に、私の質問。上記の例はセマンティクスでどのように異なりますか?
違いは明らかです。
getexamplethreeはgetexampletwoのような原子性を確保しますが、nullの返品を正しく避けますか?
はい。
getexamplethreeに他の問題はありますか?
次の電話が別の値を与えないと思われる場合のみ(別のスレッドで削除できると思われる場合)
他のヒント
メソッドには異なるセマンティクスがあります。
- GetExampleNeは原子ではありません。
- GetExampletwo新しいオブジェクトがマップに挿入された場合、nullを返します。これはgetexampleoneの動作とは異なりますが、アトミックです。
- GetExamplethreeはおそらくあなたが望むものです。それはアトミックであり、プリファーブコールの時点の後にマップにあるオブジェクトを返します。しかし、ヌルがアプリケーションで有効な値である場合、それは問題でした。 null戻り値はあいまいです。
ただし、状況に応じて、返品値を使用する時点では実際のオブジェクトではない可能性があります。その後、明示的なロックが必要です。
単に最初のバージョンを使用してメソッドを同期させてみませんか?
public synchronized V getExampleOne(K key) {
map.putIfAbsent(key, new Object());
return map.get(key);
}
それはあなたに最大の並列性を提供しませんが、あなたが2つの操作しか持っていないことも事実です getExampleThree
, 、正しいですが、読みやすく、他の誰かがあなたのコードを読んでいる人にとって理解しやすいです。
あなたがトリックはあなたが非原子であると仮定し、それを処理することだと思うと思います。
私はあなたが探しているものを本当に明確にしていません。これが接線でオフになっているかどうかを教えてください。私は修正します。
おそらくあなたは次のようなものを探しています:
private final ConcurrentHashMap<String, Object> map = new ConcurrentHashMap();
/*
* Guaranteed to return the object replaced.
*
* Note that by the time this method returns another thread
* may have already replaced the object again.
*
* All we can guarantee here is that the return value WAS
* associated with the key in the map at the time the entry was
* replaced.
*
* A null return implies that either a null object was associated
* with the specified key or there was no entry in the map for
* the specified key wih 'null' as it's 'value'.
*/
public Object consistentReplace ( String key, Object newValue ) {
Object oldValue = map.get(key);
while ( !map.replace(key, oldValue, newValue) ) {
// Failed to replace because someone got in there before me.
oldValue = map.get(key);
}
return oldValue;
}