A iteração de valores do ConcurrentHashMap é segura para encadeamento?

No javadoc para ConcurrentHashMap é o seguinte:

As operações de recuperação (incluindo get) geralmente não são bloqueadas, portanto podem se sobrepor às operações de atualização (incluindo colocar e remover). As recuperações refletem os resultados das operações de atualização mais recentes concluídas no início. Para operações agregadas, como putAll e clear, as recuperações simultâneas podem refletir a inserção ou a remoção de apenas algumas inputs. Da mesma forma, Iteradores e Enumerações retornam elementos que refletem o estado da tabela de hash em algum momento ou desde a criação do iterador / enumeração. Eles não lançam ConcurrentModificationException. No entanto, os iteradores são projetados para serem usados ​​por apenas um thread por vez.

O que isso significa? O que acontece se eu tentar iterar o mapa com dois threads ao mesmo tempo? O que acontece se eu colocar ou remover um valor do mapa enquanto o iterar?

    O que isso significa?

    Isso significa que cada iterador obtido de um ConcurrentHashMap foi projetado para ser usado por um único encadeamento e não deve ser passado adiante. Isso inclui o açúcar sintático que o loop for-each fornece.

    O que acontece se eu tentar iterar o mapa com dois threads ao mesmo tempo?

    Ele funcionará como esperado se cada um dos encadeamentos usar seu próprio iterador.

    O que acontece se eu colocar ou remover um valor do mapa enquanto o iterar?

    É garantido que as coisas não vão quebrar se você fizer isso (isso é parte do que significa o “concorrente” em ConcurrentHashMap ). No entanto, não há garantia de que um thread verá as alterações no mapa que o outro thread executa (sem obter um novo iterador do mapa). O iterador é garantido para refletir o estado do mapa no momento de sua criação. Futuras alterações podem ser refletidas no iterador, mas não precisam ser.

    Em conclusão, uma declaração como

     for (Object o : someConcurrentHashMap.entrySet()) { // ... } 

    vai ficar bem (ou pelo menos seguro) quase toda vez que você vê.

    Você pode usar esta class para testar dois encadeamentos de access e um mutando a instância compartilhada de ConcurrentHashMap :

     import java.util.Map; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ConcurrentMapIteration { private final Map map = new ConcurrentHashMap(); private final static int MAP_SIZE = 100000; public static void main(String[] args) { new ConcurrentMapIteration().run(); } public ConcurrentMapIteration() { for (int i = 0; i < MAP_SIZE; i++) { map.put("key" + i, UUID.randomUUID().toString()); } } private final ExecutorService executor = Executors.newCachedThreadPool(); private final class Accessor implements Runnable { private final Map map; public Accessor(Map map) { this.map = map; } @Override public void run() { for (Map.Entry entry : this.map.entrySet()) { System.out.println( Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']' ); } } } private final class Mutator implements Runnable { private final Map map; private final Random random = new Random(); public Mutator(Map map) { this.map = map; } @Override public void run() { for (int i = 0; i < 100; i++) { this.map.remove("key" + random.nextInt(MAP_SIZE)); this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString()); System.out.println(Thread.currentThread().getName() + ": " + i); } } } private void run() { Accessor a1 = new Accessor(this.map); Accessor a2 = new Accessor(this.map); Mutator m = new Mutator(this.map); executor.execute(a1); executor.execute(m); executor.execute(a2); } } 

    Nenhuma exceção será lançada.

    Compartilhar o mesmo iterador entre encadeamentos do acessador pode levar a um impasse:

     import java.util.Iterator; import java.util.Map; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ConcurrentMapIteration { private final Map map = new ConcurrentHashMap(); private final Iterator> iterator; private final static int MAP_SIZE = 100000; public static void main(String[] args) { new ConcurrentMapIteration().run(); } public ConcurrentMapIteration() { for (int i = 0; i < MAP_SIZE; i++) { map.put("key" + i, UUID.randomUUID().toString()); } this.iterator = this.map.entrySet().iterator(); } private final ExecutorService executor = Executors.newCachedThreadPool(); private final class Accessor implements Runnable { private final Iterator> iterator; public Accessor(Iterator> iterator) { this.iterator = iterator; } @Override public void run() { while(iterator.hasNext()) { Map.Entry entry = iterator.next(); try { String st = Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']'; } catch (Exception e) { e.printStackTrace(); } } } } private final class Mutator implements Runnable { private final Map map; private final Random random = new Random(); public Mutator(Map map) { this.map = map; } @Override public void run() { for (int i = 0; i < 100; i++) { this.map.remove("key" + random.nextInt(MAP_SIZE)); this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString()); } } } private void run() { Accessor a1 = new Accessor(this.iterator); Accessor a2 = new Accessor(this.iterator); Mutator m = new Mutator(this.map); executor.execute(a1); executor.execute(m); executor.execute(a2); } } 

    Assim que você começar a compartilhar o mesmo Iterator

    >

    entre os segmentos do acessador e do mutador, o java.lang.IllegalStateException s começará a aparecer.

     import java.util.Iterator; import java.util.Map; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ConcurrentMapIteration { private final Map map = new ConcurrentHashMap(); private final Iterator> iterator; private final static int MAP_SIZE = 100000; public static void main(String[] args) { new ConcurrentMapIteration().run(); } public ConcurrentMapIteration() { for (int i = 0; i < MAP_SIZE; i++) { map.put("key" + i, UUID.randomUUID().toString()); } this.iterator = this.map.entrySet().iterator(); } private final ExecutorService executor = Executors.newCachedThreadPool(); private final class Accessor implements Runnable { private final Iterator> iterator; public Accessor(Iterator> iterator) { this.iterator = iterator; } @Override public void run() { while (iterator.hasNext()) { Map.Entry entry = iterator.next(); try { String st = Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']'; } catch (Exception e) { e.printStackTrace(); } } } } private final class Mutator implements Runnable { private final Random random = new Random(); private final Iterator> iterator; private final Map map; public Mutator(Map map, Iterator> iterator) { this.map = map; this.iterator = iterator; } @Override public void run() { while (iterator.hasNext()) { try { iterator.remove(); this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString()); } catch (Exception ex) { ex.printStackTrace(); } } } } private void run() { Accessor a1 = new Accessor(this.iterator); Accessor a2 = new Accessor(this.iterator); Mutator m = new Mutator(map, this.iterator); executor.execute(a1); executor.execute(m); executor.execute(a2); } } 

    Isso significa que você não deve compartilhar um object iterador entre vários encadeamentos. Criar vários iteradores e usá-los simultaneamente em encadeamentos separados é bom.

    Isso pode lhe dar uma boa visão

    O ConcurrentHashMap alcança maior concorrência, relaxando ligeiramente as promises que faz aos chamadores. Uma operação de recuperação retornará o valor inserido pela operação de inserção concluída mais recente e também poderá retornar um valor adicionado por uma operação de inserção que esteja em andamento simultaneamente (mas, em nenhum caso, retornará um resultado sem sentido). Iteradores retornados por ConcurrentHashMap.iterator () retornarão cada elemento no máximo uma vez e nunca lançarão ConcurrentModificationException, mas poderão ou não refletir inserções ou remoções ocorridas desde que o iterador foi construído . Nenhum travamento em toda a mesa é necessário (ou mesmo possível) para fornecer segurança de rosca ao iterar a coleção. ConcurrentHashMap pode ser usado como um substituto para synchronizedMap ou Hashtable em qualquer aplicativo que não dependa da capacidade de bloquear a tabela inteira para impedir atualizações.

    Em relação a este:

    No entanto, os iteradores são projetados para serem usados ​​por apenas um thread por vez.

    Isso significa que, embora o uso de iteradores produzidos pelo ConcurrentHashMap em dois segmentos seja seguro, isso pode causar um resultado inesperado no aplicativo.

    O que isso significa?

    Isso significa que você não deve tentar usar o mesmo iterador em dois segmentos. Se você tiver dois encadeamentos que precisem iterar sobre as chaves, valores ou inputs, cada um deles deverá criar e usar seus próprios iteradores.

    O que acontece se eu tentar iterar o mapa com dois threads ao mesmo tempo?

    Não está totalmente claro o que aconteceria se você quebrasse essa regra. Você poderia apenas obter um comportamento confuso, da mesma maneira que faz se (por exemplo) dois segmentos tentarem ler da input padrão sem sincronizar. Você também pode obter um comportamento não thread-safe.

    Mas se os dois threads usassem diferentes iteradores, você deveria estar bem.

    O que acontece se eu colocar ou remover um valor do mapa enquanto o iterar?

    Essa é uma questão separada, mas a seção javadoc que você citou responde adequadamente. Basicamente, os iteradores são thread-safe, mas não é definido se você verá os efeitos de quaisquer inserções, atualizações ou exclusões simultâneas refletidas na seqüência de objects retornados pelo iterador. Na prática, provavelmente depende de onde no mapa as atualizações ocorrem.