A classe Java ConcurrentHashMap faz parte das Classes de Coleções Concorrentes. É uma implementação de tabela de hash que suporta recuperação e atualizações concorrentes. É utilizada em ambientes de múltiplas threads para evitar a ConcurrentModificationException.
ConcurrentHashMap
Se tentarmos modificar a coleção enquanto iteramos sobre ela, obtemos a exceção ConcurrentModificationException
. A versão 1.5 do Java 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 valores ou chaves nulos.
Exemplo de Java ConcurrentHashMap
A classe ConcurrentHashMap
é semelhante à 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);
}
}
Saída:
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)
É claro 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 rastreamento da exceção. A seguinte declaração lançou 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 iterator sabe sobre a modificação na Collection?
Tomamos o conjunto de chaves do HashMap e então iteramos sobre ele. O HashMap contém uma variável para contar o número de modificações, e o iterator a usa 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 o código um pouco para sair do loop do iterator quando inserimos a nova entrada. Tudo o que precisamos fazer é adicionar uma instrução break após a chamada de 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 mudar 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ê percebeu aqueles colchetes angulares ao criar nosso objeto de coleção e Iterador? É chamado de genéricos e é muito poderoso quando se trata de verificação de tipo em tempo de compilação para remover ClassCastException em tempo de execução. Saiba mais sobre genéricos em Exemplo de Genéricos Java. Você também deve ler Perguntas de Entrevista sobre Coleções Java e Padrão de Design Iterador em Java.
Você pode conferir mais exemplos de coleções Java em nosso Repositório GitHub.
Referência: Doc API
Source:
https://www.digitalocean.com/community/tutorials/concurrenthashmap-in-java