2017-06-30 7 views
1

ConcurrentHashMapを使用するコードをいくつか持ち、私が行っていることが正しいかどうかを確認したいと思います。ConcurrentHashMap:コレクションを値として保存するときの同期の必要性

マップには文字列をキーとして、コレクションには値(Set)を格納します。私は当初、原子的にいくつかの操作(put、removeなど)を行うためのロックストライピングパターンを使用する実装を持っていましたが、その代わりにcomputeIfAbsent/putIfAbsent(put用)とcomputeIfAbsent(remove用)を使用できることを認識しました。複数のスレッドが4つのメソッド(get、put、removeKey、removeValue)をすべて呼び出します。

私のAFTERコードが正しいのか、スレッドセーフであるのかは誰にでも分かりますか?前もって感謝します!

private final ConcurrentMap<K, Set<V>> map = new ConcurrentHashMap<>(); 
private final ConcurrentMap<K, Object> locks = new ConcurrentHashMap<>(); 

public Set<V> getValue(K key) { 
    if (map.get(key) == null) return null; 
    return map.get(key).stream().map(k -> k.getValue()).collect(Collectors.toSet()); 
} 

private void putValue(K key, V value) { 
     Object lock = locks.putIfAbsent(key, new Object()); 
    if (lock == null) lock = locks.get(key); 
     Set<V> existingSet = map.computeIfAbsent(key, k -> { 
      Set<V> values = new ConcurrentSkipListSet<>(MY_COMPARATOR); 
      values.add(value); 
      return values; 
     }); 
     if (existingSet != null) { // <- my guess is that this is unnecessary 
      synchronized (lock) { 
       map.get(key).add(expiringValue); 
      } 
     } 
    } 

public void removeKey(K key) { 
    if (key == null) return; 
    if (map.get(key) != null) { 
     Object lock = locks.get(key); 
     synchronized (lock) { 
      map.remove(key); 
     } 
     locks.remove(key); 
    } 
} 

public void removeValue(K key, V value) { 
    if (key == null || value == null) return; 
    if (map.get(key) != null) { 
     Object lock = locks.get(key); 
     synchronized (lock) { 
      map.get(key).remove(value); 
      if (map.get(key).size() == 0) { 
       map.remove(key); 
      } 
     } 
    } 
} 

private void putValue(K key, V value) { 
    map.computeIfAbsent(key, k -> new ConcurrentSkipListSet<>(MY_COMPARATOR)).add(value); 
} 

public void removeKey(K key) { 
    map.remove(key); 
} 

public void removeValue(K key, V value) { 
    Set<ExpiringValue<V>> resultingValues = map.computeIfPresent(key, (k, values) -> { 
     values.remove(value); 
     return values; 
    }); 
    if (resultingValues != null && resultingValues.size() == 0) { 
     map.remove(key); 
    } 
} 
+0

コレクション値を初期化するために 'computeIfAbsent'を使用するのは、まだ存在しない場合です。 – Antoniossss

答えて

1

後に少なくとも1つの可能な問題は、あなたがあなたのputValueを使用して値を失うことができるということになる前に。 computeIfAbsentはスレッドセーフですが、add()はもう存在しません。したがって、あなたは(あなたのロジックに応じて)次のシナリオを思い付くことができます。すべてのアクションがアトミックであることを

private void putValue(K key, V value) { 
    map.compute(key, (k, values) -> { 
     if (values == null) { 
      values = new ConcurrentSkipListSet<>(MY_COMPARATOR); 
     } 
     values.add(value); 
     return values; 
    }); 
} 

public void removeKey(K key) { 
    map.remove(key); // No change. 
} 

public void removeValue(K key, V value) { 
    map.computeIfPresent(key, (k, values) -> { 
     values.remove(value); 
     return values.isEmpty() ? null : values; 
    }); 
} 

これらの保証:

T1: computeIfAbsent creates the set for key K 
T2: removes K 
T1: performs add on the set, which is no longer in the map 
+0

これは正しい観察です。私はセットがマップからも削除されているという事実を拾いませんでした。 –

+0

それで、 'T2'が' K'を削除した場合、 'T1'の前か後に' set'に値が追加されますか?どちらの場合でも、set *には値があり、map *には 'K'キーがないので、(他の場所で参照されていなければ)そのセットには到達できません。 ---私は 'computeIfAbsent()'と 'add()'の分離に問題はないと言っているわけではありませんが、記述したシナリオはそうではありません。 – Andreas

+0

@Andreasそれは物事の壮大な計画では問題ではないかもしれません。それはまだ失われた更新プログラムであり、実際のコードに応じて問題になる可能性があります。少なくともputValueはアトミックではないことを意味します。 – Kayaman

1

私はあなたができる最も安定したが、このようなものですね。 でcomputeを使用すると、ちょっとぎこちない感じがしますが、ConcurrentHashMapはその動作を保証します。関数がnullを返し

場合は、マッピングが削除されます。

computeIfPresentnullを返す再マッピング機能は、すでにやろうとして何をすべきかだけの正しい方法です。

+0

"computeIfPresentでnullを返す再マッピング関数は、あなたがすでにやろうとしていたことを行う正しい方法です" - 私はこの事実を完全に逃しました。 – user788052

関連する問題