La classe Java ConcurrentHashMap fa parte delle classi di raccolta di concorrenza. È un’implementazione di una tabella hash che supporta il recupero e gli aggiornamenti simultanei. Viene utilizzata in un ambiente multithreading per evitare ConcurrentModificationException.
ConcurrentHashMap
Se proviamo a modificare la raccolta mentre la stiamo iterando, otteniamo ConcurrentModificationException
. Java 1.5 ha introdotto classi concorrenti nel pacchetto java.util.concurrent
per superare questo scenario. ConcurrentHashMap è l’implementazione di Map che ci consente di modificare la Map durante l’iterazione. Le operazioni di ConcurrentHashMap sono thread-safe. ConcurrentHashMap non consente valori nulli per chiavi e valori.
Esempio di Java ConcurrentHashMap
La classe ConcurrentHashMap
è simile a HashMap, tranne che è thread-safe e consente la modifica durante l’iterazione.
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);
}
}
Output:
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)
È evidente dall’output che ConcurrentHashMap si occupa della nuova voce nella mappa durante l’iterazione, mentre HashMap lancia ConcurrentModificationException
. Diamo un’occhiata da vicino alla traccia dello stack dell’eccezione. La seguente istruzione ha generato l’eccezione.
String key = it1.next();
Ciò significa che la nuova voce è stata inserita nella HashMap ma l’iteratore sta fallendo. In realtà, l’iteratore sugli oggetti Collection è fail-fast, ovvero qualsiasi modifica nella struttura o nel numero di voci nell’oggetto di collezione attiverà l’eccezione.
Come fa l’iteratore a sapere della modifica nella collezione?
Abbiamo preso l’insieme di chiavi dalla HashMap e poi iterando su di esso. HashMap contiene una variabile per contare il numero di modifiche e l’iteratore la utilizza quando si chiama la sua funzione next() per ottenere la voce successiva. 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;
Modifichiamo un po’ il codice per uscire dal ciclo dell’iteratore quando inseriamo la nuova voce. Tutto quello che dobbiamo fare è aggiungere una dichiarazione di break dopo la chiamata put.
if(key.equals("3")){
myMap.put(key+"new", "new3");
break;
}
L’output con il codice sopra:
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}
Cosa succede se il valore della chiave viene modificato?
Cosa succede se non aggiungiamo una nuova voce ma aggiorniamo la coppia chiave-valore esistente? Lancerà un’eccezione? Modifichiamo il codice nel programma originale e controlliamolo.
//myMap.put(key+"new", "new3");
myMap.put(key, "new3");
Non ci sarà alcuna eccezione perché la collezione viene modificata ma la sua struttura rimane la stessa.
Ulteriori Letture
Hai notato quei simboli angolari durante la creazione del nostro oggetto di collezione e Iterator? Si chiama generics ed è molto potente quando si tratta di controllo dei tipi al momento della compilazione per rimuovere ClassCastException al runtime. Scopri di più sui generics in Esempio di Generics in Java. Dovresti anche leggere Domande di Intervista sulle Collezioni Java e Modello di Progettazione dell’Iterator in Java.
Puoi controllare più esempi di collezioni Java dal nostro Repository GitHub.
Riferimento: Doc API
Source:
https://www.digitalocean.com/community/tutorials/concurrenthashmap-in-java