Avec ConcurrentHashMap, quand la synchronisation est-elle nécessaire?

J’ai un ConcurrentHashMap où je fais ce qui suit:

sequences = new ConcurrentHashMap<Class, AtomicLong>(); if(!sequences.containsKey(table)) { synchronized (sequences) { if(!sequences.containsKey(table)) initializeHashMapKeyValue(table); } } 

Ma question est la suivante: est-il inutile de faire un supplément?

 if(!sequences.containsKey(table)) 

Vérifiez à l’intérieur du bloc synchronisé afin que les autres threads n’initialisent pas la même valeur de hashmap?

Peut-être que le chèque est nécessaire et que je me trompe? Ce que je fais semble un peu ridicule, mais je pense que c’est nécessaire.

Toutes les opérations sur un ConcurrentHashMap sont thread-safe, mais les opérations thread-safe ne sont pas composables. Vous essayez de faire une paire d’opérations atomiques: rechercher quelque chose sur la carte et, au cas où ce ne serait pas là, mettre quelque chose là (je suppose). Donc, la réponse à vos questions est oui , vous devez vérifier à nouveau et votre code a l’air correct.

Vous devriez utiliser les méthodes putIfAbsent de ConcurrentMap .

 ConcurrentMap map = new ConcurrentHashMap (); public long addTo(Ssortingng key, long value) { // The final value it became. long result = value; // Make a new one to put in the map. AtomicLong newValue = new AtomicLong(value); // Insert my new one or get me the old one. AtomicLong oldValue = map.putIfAbsent(key, newValue); // Was it already there? Note the deliberate use of '!='. if ( oldValue != newValue ) { // Update it. result = oldValue.addAndGet(value); } return result; } 

Pour les puristes fonctionnels parmi nous, ce qui précède peut être simplifié (ou peut-être complexifié) pour:

 public long addTo(Ssortingng key, long value) { return map.putIfAbsent(key, new AtomicLong()).addAndGet(value); } 

Et en Java 8, nous pouvons éviter la création inutile d’un AtomicLong :

 public long addTo8(Ssortingng key, long value) { return map.computeIfAbsent(key, k -> new AtomicLong()).addAndGet(value); } 

Vous ne pouvez pas obtenir un verrou exclusif avec ConcurrentHashMap . Dans ce cas, vous devriez mieux utiliser Synchronized HashMap.

Il existe déjà une méthode atomique à insérer dans ConcurrentHashMap si l’object n’y est pas déjà; putIfAbsent

Je vois ce que tu as fait là 😉 la question est tu le vois toi-même?

Tout d’abord, vous avez utilisé quelque chose appelé «schéma de locking vérifié». Où vous avez un raccourci (premier contenu) qui n’a pas besoin de synchronisation si c’est le cas, et un chemin lent qui doit être synchronisé car vous effectuez une opération complexe. Votre opération consiste à vérifier si quelque chose se trouve à l’intérieur de la carte, puis à y mettre quelque chose / à l’initialiser. Donc, peu importe que ConcurrentHashMap soit thread-safe pour une opération unique, car vous effectuez deux opérations simples qui doivent être traitées comme une unité, de sorte que ce bloc synchronisé est correct et qu’il peut en réalité être synchronisé avec autre chose, par exemple this .

En Java 8, vous devriez pouvoir remplacer le verrou à double contrôle par .computeIfAbsent :

 sequences.computeIfAbsent(table, k -> initializeHashMapKeyValue(k)); 

Créez un fichier nommé dictionary.txt avec le contenu suivant:

 a as an b bat ball 

Nombre de mots commençant par “a”: 3

Nombre de mots commençant par “b”: 3

Nombre total de mots: 6

Maintenant, exécutez le programme suivant en tant que: java WordCount test_dictionary.txt 10

 public class WordCount { Ssortingng fileName; public WordCount(Ssortingng fileName) { this.fileName = fileName; } public void process() throws Exception { long start = Instant.now().toEpochMilli(); LongAdder totalWords = new LongAdder(); //Map wordCounts = Collections.synchronizedMap(new HashMap()); ConcurrentHashMap wordCounts = new ConcurrentHashMap(); Files.readAllLines(Paths.get(fileName)) .parallelStream() .map(line -> line.split("\\s+")) .flatMap(Arrays::stream) .parallel() .map(Ssortingng::toLowerCase) .forEach(word -> { totalWords.increment(); char c = word.charAt(0); if (!wordCounts.containsKey(c)) { wordCounts.put(c, new LongAdder()); } wordCounts.get(c).increment(); }); System.out.println(wordCounts); System.out.println("Total word count: " + totalWords); long end = Instant.now().toEpochMilli(); System.out.println(Ssortingng.format("Completed in %d milliseconds", (end - start))); } public static void main(Ssortingng[] args) throws Exception { for (int r = 0; r < Integer.parseInt(args[1]); r++) { new WordCount(args[0]).process(); } } 

}

Vous verriez que les nombres varient comme ci-dessous

{a = 2, b = 3}

Nombre total de mots: 6

Terminé en 77 millisecondes

{a = 3, b = 3}

Nombre total de mots: 6

Maintenant, commentez ConcurrentHashMap à la ligne 13, décommentez la ligne au-dessus et réexécutez le programme.

Vous verriez des comptes déterministes.