ConcurrentHashMap en Java

La classe Java ConcurrentHashMap fait partie des classes de collections de concurrence. C’est une implémentation de table de hachage, qui prend en charge la récupération et les mises à jour concurrentes. Elle est utilisée dans un environnement multithread pour éviter la ConcurrentModificationException.

ConcurrentHashMap

Si nous essayons de modifier la collection pendant que nous la parcourons, nous obtenons une ConcurrentModificationException. Java 1.5 a introduit les classes Concurrent dans le package java.util.concurrent pour surmonter ce scénario. ConcurrentHashMap est l’implémentation de Map qui nous permet de modifier la Map pendant l’itération. Les opérations de ConcurrentHashMap sont sécurisées pour les threads. ConcurrentHashMap n’accepte pas null pour les clés et les valeurs.

Exemple de Java ConcurrentHashMap

La classe ConcurrentHashMap est similaire à HashMap, sauf qu’elle est sécurisée pour les threads et permet la modification pendant l’itération.

package com.journaldev.util;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {

	public static void main(String[] args) {

		//ConcurrentHashMap
		Map<String,String> myMap = new ConcurrentHashMap<String,String>();
		myMap.put("1", "1");
		myMap.put("2", "1");
		myMap.put("3", "1");
		myMap.put("4", "1");
		myMap.put("5", "1");
		myMap.put("6", "1");
		System.out.println("ConcurrentHashMap before iterator: "+myMap);
		Iterator<String> it = myMap.keySet().iterator();

		while(it.hasNext()){
			String key = it.next();
			if(key.equals("3")) myMap.put(key+"new", "new3");
		}
		System.out.println("ConcurrentHashMap after iterator: "+myMap);

		//HashMap
		myMap = new HashMap<String,String>();
		myMap.put("1", "1");
		myMap.put("2", "1");
		myMap.put("3", "1");
		myMap.put("4", "1");
		myMap.put("5", "1");
		myMap.put("6", "1");
		System.out.println("HashMap before iterator: "+myMap);
		Iterator<String> it1 = myMap.keySet().iterator();

		while(it1.hasNext()){
			String key = it1.next();
			if(key.equals("3")) myMap.put(key+"new", "new3");
		}
		System.out.println("HashMap after iterator: "+myMap);
	}

}

Sortie:

ConcurrentHashMap before iterator: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}
ConcurrentHashMap after iterator: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}
HashMap before iterator: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
	at java.util.HashMap$KeyIterator.next(HashMap.java:828)
	at com.test.ConcurrentHashMapExample.main(ConcurrentHashMapExample.java:44)

Il est clair à partir de la sortie que ConcurrentHashMap gère la nouvelle entrée dans la carte tout en itérant, tandis que HashMap lance ConcurrentModificationException. Examinons de près la trace de la pile de l’exception. La déclaration suivante a généré une exception.

String key = it1.next();

Cela signifie que la nouvelle entrée a été insérée dans HashMap mais que l’itérateur échoue. En fait, l’itérateur sur les objets Collection est fail-fast, c’est-à-dire que toute modification de la structure ou du nombre d’entrées dans l’objet de collection déclenchera l’exception.

Comment l’itérateur sait-il quelle modification a été apportée à la Collection?

Nous avons pris l’ensemble des clés de HashMap, puis nous itérons dessus. HashMap contient une variable pour compter le nombre de modifications et l’itérateur l’utilise lorsque vous appelez sa fonction next() pour obtenir l’entrée suivante. HashMap.java:

/**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     */
    transient volatile int modCount;

Modifions un peu le code pour sortir de la boucle de l’itérateur lorsque nous insérons la nouvelle entrée. Tout ce que nous avons à faire, c’est d’ajouter une instruction break après l’appel à put.

if(key.equals("3")){
	myMap.put(key+"new", "new3");
	break;
}

La sortie avec le code ci-dessus:

ConcurrentHashMap before iterator: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}
ConcurrentHashMap after iterator: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}
HashMap before iterator: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}
HashMap after iterator: {3=1, 2=1, 1=1, 3new=new3, 6=1, 5=1, 4=1}

Que se passe-t-il si la valeur de la clé est modifiée?

Que se passe-t-il si nous n’ajoutons pas une nouvelle entrée mais mettons à jour la paire clé-valeur existante? Est-ce que cela va déclencher une exception? Changeons le code dans le programme original et vérifions.

//myMap.put(key+"new", "new3");
myMap.put(key, "new3");

Il n’y aura aucune exception car la collection est modifiée mais sa structure reste la même.

Lecture complémentaire

Avez-vous remarqué ces crochets angulaires lors de la création de notre objet de collection et de l’itérateur? C’est ce qu’on appelle les génériques et c’est très puissant lorsqu’il s’agit de vérification des types au moment de la compilation pour supprimer les ClassCastException lors de l’exécution. Apprenez-en davantage sur les génériques dans Exemple de génériques Java. Vous devriez également lire les Questions d’entretien sur les collections Java et le Modèle de conception de l’itérateur en Java.

Vous pouvez consulter plus d’exemples de collections Java sur notre Dépôt GitHub.

Référence : Documentation de l’API

Source:
https://www.digitalocean.com/community/tutorials/concurrenthashmap-in-java