J'ai une instance concurrenteHashMap à laquelle certains threads ajoutent des entrées. Les valeurs sont des entiers.
Simultanément, d'autres threads souhaitent récupérer la somme de toutes les valeurs de la carte. Je souhaite que ces fils voient une valeur cohérente. Cependant, il n'est pas nécessaire qu'ils voient toujours la dernière valeur.
Le thread de code suivant est-il sûr?
import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class MyClass { private Map<Integer, Integer> values = new ConcurrentHashMap<>(); public void addValue(Integer key, int value){ values.put(key, value); } public long sumOfValues(){ return values .values() .stream() .mapToInt(Integer::intValue) .sum(); } }
La somme opération être calculée sur un ensemble cohérent de valeurs?
Lorsque l'opération de somme aura lieu, les appels à put () seront-ils bloqués?
Bien sûr, je pourrais synchroniser l'accès moi-même, et même diviser les verrous de lecture et d'écriture pour permettre accès en lecture simultané et accès en écriture synchronisé, mais je suis curieux de savoir si c'est nécessaire lors de l'utilisation de simultanéHashMap comme implémentation de collection.
3 Réponses :
L'intérêt de ConcurrentHashMap
est que les entrées sont aussi indépendantes que possible les unes des autres. Il n'y a pas une vue cohérente de l'ensemble de la carte. En effet, même la taille
ne renvoie pas une valeur très utile.
Je devrais donc probablement utiliser un verrou de lecture et d'écriture pour garantir des valeurs cohérentes. Je peux mémoriser la somme dans l'écriture addValue aussi je suppose.
@ThatDataGuy Yup. Vous pouvez utiliser un AtomicInteger
pour garder le total, mais la mise à jour serait compliquée et ne serait pas beaucoup / aucun avantage de toute façon.
@ThatDataGuy cela soulève la question du type de cohérence que vous attendez en présence de mises à jour simultanées. Le résultat est obsolète dès que vous le recevez.
La documentation dit à propos des keySet ()
et entrySet ()
de ConcurrentHashMap
: Les itérateurs et séparateurs de la vue sont peu cohérents. < / p>
Peu cohérent caractérisé comme
Alors ...
Le thread de code suivant est-il sûr?
Oui, au sens étroit de ConcurrentModificationException absente ou d'incohérences internes du HashMap.
L'opération de somme sera-t-elle calculée sur un ensemble cohérent de valeurs?
sur un ensemble peu cohérent
Lorsque l'opération de somme a lieu, les appels à put () seront-ils bloqués?
Non
Si vous avez besoin d'interroger la somme simultanément, une solution consiste à écrire une classe wrapper qui maintient à la fois l'état de la carte et la somme, en utilisant un LongAdder
pour maintenir atomiquement la somme.
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.LongAdder; public class MapSum { private final ConcurrentMap<Integer, Integer> map = new ConcurrentHashMap<>(); private final LongAdder sum = new LongAdder(); public Integer get(Integer k) { return map.get(k); } public Integer put(Integer k, Integer v) { Integer[] out = new Integer[1]; map.compute(k, (_k, old) -> { out[0] = old; // cast to long to avoid overflow sum.add((long) v - (old != null ? old : 0)); return v; }); return out[0]; } public Integer remove(Integer k) { Integer[] out = new Integer[1]; map.compute(k, (_k, old) -> { out[0] = old; // cast to long to avoid overflow; -Integer.MIN_VALUE == Integer.MIN_VALUE if(old != null) { sum.add(- (long) old); } return null; }); return out[0]; } public long sum() { return sum.sum(); } }
Ceci a l'avantage supplémentaire d'interroger la somme en temps O (1) au lieu de temps O ( n ). Vous pouvez ajouter d'autres méthodes Map
si vous le souhaitez, et même implémenter Map
- veillez simplement à conserver la somme lorsque vous modifiez le contenu de la carte de quelque manière que ce soit .
Cela ne garantit pas la cohérence; un appel remove
pourrait encore soustraire une valeur que l'appel put
simultané n'a pas encore ajoutée, résultant en une somme qui n'a jamais existé conceptuellement.
Très bon point! Je l'ai modifié pour utiliser la méthode compute
, qui devrait mettre à jour sum
tant que le verrou de cette clé est toujours maintenu. Espérons que cela résout le problème.