A classe Java ConcurrentHashMap faz parte das Classes de Coleção de Concorrência. É uma implementação de tabela de hash que suporta recuperação e atualizações concorrentes. É usada em um ambiente de várias threads para evitar ConcurrentModificationException.
ConcurrentHashMap
Se tentarmos modificar a coleção enquanto iteramos sobre ela, obtemos ConcurrentModificationException
. O Java 1.5 introduziu classes Concorrentes no pacote java.util.concurrent
para superar esse cenário. ConcurrentHashMap é a implementação de Map que nos permite modificar o Map durante a iteração. As operações do ConcurrentHashMap são seguras para threads. ConcurrentHashMap não permite nulos para chaves e valores.
Exemplo de ConcurrentHashMap em Java
A classe ConcurrentHashMap
é semelhante a HashMap, exceto que é segura para threads e permite modificações durante a iteração.
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 a partir da saída que o ConcurrentHashMap cuida da nova entrada no mapa durante a iteração, enquanto o HashMap lança ConcurrentModificationException
. Vamos analisar de perto a pilha de exceções. A seguinte declaração gerou a exceção.
String key = it1.next();
Isso significa que a nova entrada foi inserida no HashMap, mas o Iterator está falhando. Na verdade, o Iterator em objetos Collection é fail-fast, ou seja, qualquer modificação na estrutura ou no número de entradas no objeto de coleção acionará a exceção.
Como o iterador sabe sobre a modificação na Collection?
Tomamos o conjunto de chaves do HashMap e, em seguida, iteramos sobre ele. O HashMap contém uma variável para contar o número de modificações, e o iterador a utiliza quando você chama sua função next() para obter a próxima entrada. 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;
Vamos alterar um pouco o código para sair do loop do iterador quando inserirmos a nova entrada. Tudo o que precisamos fazer é adicionar uma instrução break após a chamada do put.
if(key.equals("3")){
myMap.put(key+"new", "new3");
break;
}
A saída com o código acima:
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}
O que acontece se o valor da chave for modificado?
O que acontece se não adicionarmos uma nova entrada, mas atualizarmos o par chave-valor existente? Vai lançar uma exceção? Vamos alterar o código no programa original e verificar.
//myMap.put(key+"new", "new3");
myMap.put(key, "new3");
Não haverá nenhuma exceção porque a coleção é modificada, mas sua estrutura permanece a mesma.
Leitura Adicional
Você notou aqueles colchetes angulares ao criar nosso objeto de coleção e Iterator? Chama-se generics e é muito poderoso quando se trata de verificação de tipo em tempo de compilação para evitar ClassCastException em tempo de execução. Saiba mais sobre generics em Exemplo de Java Generics. Você também deve ler Perguntas de Entrevista sobre Coleções Java e Padrão de Design Iterator em Java.
Você pode conferir mais exemplos de coleções Java em nosso Repositório GitHub.
Referência: Documentação da API
Source:
https://www.digitalocean.com/community/tutorials/concurrenthashmap-in-java