Qual é a diferença entre ConcurrentHashMap e Collections.synchronizedMap (Map)?


607

Eu tenho um mapa que deve ser modificado por vários segmentos simultaneamente.

Parece haver três implementações diferentes de mapas sincronizados na API Java:

  • Hashtable
  • Collections.synchronizedMap(Map)
  • ConcurrentHashMap

Pelo que entendi, Hashtableé uma implementação antiga (estendendo a Dictionaryclasse obsoleta ), que foi adaptada posteriormente para se ajustar à Mapinterface. Embora esteja sincronizado, parece ter sérios problemas de escalabilidade e é desencorajado para novos projetos.

Mas e os outros dois? Quais são as diferenças entre mapas devolvidos por Collections.synchronizedMap(Map)e ConcurrentHashMaps? Qual deles se encaixa em qual situação?


7
@SmilesinaJar Link está atualmente quebrado, aqui é uma cópia arquivada deste artigo: Por que ConcurrentHashMap é melhor do que Hashtable e tão bom como um HashMap
informatik01

2
IBM: Como o ConcurrentHashMap oferece maior concorrência sem comprometer a segurança do encadeamento @ ibm.com/developerworks/java/library/j-jtp08223/…
pramodc84

FYI, o Java 6 trouxe ConcurrentSkipListMapcomo outra Mapimplementação segura para threads . Projetado para ser altamente simultâneo sob carga, usando o algoritmo Skip List .
Basil Bourque

Respostas:


423

Para suas necessidades, use ConcurrentHashMap. Permite a modificação simultânea do mapa a partir de vários threads, sem a necessidade de bloqueá-los. Collections.synchronizedMap(map)cria um mapa de bloqueio que prejudicará o desempenho, embora garanta consistência (se usado corretamente).

Use a segunda opção se precisar garantir a consistência dos dados, e cada encadeamento precisa ter uma visualização atualizada do mapa. Use o primeiro se o desempenho for crítico e cada thread inserir apenas dados no mapa, com leituras acontecendo com menos frequência.


8
Olhando para o código-fonte, o mapa sincronizado é apenas uma aplicação com um mutex (bloqueio), enquanto o ConcurrentHashMap é mais complexo para lidar com o acesso simultâneo
Vinze

123
Observe também que o ConcurrentHashMap não permite valores ou chaves nulas. Portanto, eles NÃO são alternativas iguais a um mapa sincronizado.
onejigtwojig


5
@AbdullahShaikh O problema levantado nesse artigo foi corrigido no Java 7 e outras melhorias foram feitas no Java 8. #
Pulse0ne

5
@ hengxin: assim que você estiver executando uma operação que consiste em várias consultas ou atualizações do mapa ou quando estiver iterando sobre o mapa, precisará sincronizar manualmente no mapa para garantir consistência. Os mapas sincronizados garantem consistência apenas para operações únicas (invocações de métodos) no mapa, o que o torna mais inútil, já que a maioria das operações da vida real não é trivial, portanto, você deve sincronizar manualmente de qualquer maneira.
Holger

241
╔═══════════════╦═══════════════════╦═══════════════════╦═════════════════════╗
║   Property    ║     HashMap       ║    Hashtable      ║  ConcurrentHashMap  ║
╠═══════════════╬═══════════════════╬═══════════════════╩═════════════════════╣ 
║      Null     ║     allowed       ║              not allowed                ║
║  values/keys  ║                   ║                                         ║
╠═══════════════╬═══════════════════╬═════════════════════════════════════════╣
║ Thread-safety ║                   ║                                         ║
║   features    ║       no          ║                  yes                    ║
╠═══════════════╬═══════════════════╬═══════════════════╦═════════════════════╣
║     Lock      ║       not         ║ locks the whole   ║ locks the portion   ║        
║  mechanism    ║    applicable     ║       map         ║                     ║ 
╠═══════════════╬═══════════════════╩═══════════════════╬═════════════════════╣
║   Iterator    ║               fail-fast               ║ weakly consistent   ║ 
╚═══════════════╩═══════════════════════════════════════╩═════════════════════╝

Em relação ao mecanismo de bloqueio: Hashtable bloqueia o objeto , enquanto ConcurrentHashMapbloqueia apenas o balde .


13
Hashtablenão está bloqueando parte do mapa. Veja a implementação. Ele está usando uma synchronizedchave sem trava fornecida, o que significa basicamente que ela trava inteira hashtableem cada operação.
precisa saber é o seguinte

6
E o synchronMap?
Samuel Edwin Ward

3
Comportamento Collections.syncronizedMap é como o mapa de apoio, exceto todos os métodos são thread-safe
Sergii Shevchyk

5
Eu imprimia a tabela e a vendia por US $ 5 cada;). Bom dia @shevchyk
realPK

Editado: nem são totalmente seguros para threads. Isso é um pouco enganador para desenvolvedores mais novos. Consulte: ibm.com/developerworks/java/library/j-jtp07233/index.html para entender que mesmo o ConcurrentHashMap não é totalmente seguro para threads em relação a corridas de dados externas. (por exemplo: 1 thread remove um valor e outro tenta verificar se ele está presente e, se não, colocá-lo. Essa é uma condição de corrida de dados e ainda significa que, apesar de usar um "ConcurrentHashMap", você não é aliviado por todos os problemas de segurança de threads.
Zombies

142

Os "problemas de escalabilidade" de Hashtableestão presentes exatamente da mesma maneira Collections.synchronizedMap(Map)- eles usam sincronização muito simples, o que significa que apenas um encadeamento pode acessar o mapa ao mesmo tempo.

Isso não é muito problemático quando você tem inserções e pesquisas simples (a menos que você faça isso intensivamente), mas se torna um grande problema quando você precisa percorrer todo o mapa, o que pode levar muito tempo para um mapa grande - enquanto um thread faz isso, todos os outros precisam esperar se quiserem inserir ou procurar alguma coisa.

Ele ConcurrentHashMapusa técnicas muito sofisticadas para reduzir a necessidade de sincronização e permitir acesso de leitura paralelo por vários threads sem sincronização e, mais importante, fornece um Iteratorque não requer sincronização e até permite que o Mapa seja modificado durante a interação (embora não garanta se nenhum elemento que foi inserido durante a iteração será retornado).


4
Agora era isso que eu queria! :) O Iterator não sincronizado é apenas pura doçura! Obrigado pela informação! :) (:
Kounavi

Ótima resposta .. mas isso significa que durante o thread de recuperação não receberá as atualizações mais recentes, pois os threads do leitor não estão sincronizados.
MrA

@MrA: Você está perguntando sobre o ConcurrentHashMap? E o que você quer dizer com "recuperação"?
Michael Borgwardt

4
@ Michael Borgwardt para ConcurrentHashmap, por exemplo. suponha que haja vários threads. alguns deles estão atualizando o mapa e outros estão obtendo dados desse mesmo mapa. Portanto, nesse cenário, quando os threads estão tentando ler, é garantido que eles obtenham os dados mais recentes que foram atualizados, pois os threads do leitor não precisam reter bloqueios.
MrA

35

O ConcurrentHashMap é preferido quando você pode usá-lo - embora exija pelo menos o Java 5.

Ele foi projetado para dimensionar bem quando usado por vários threads. O desempenho pode ser marginalmente pior quando apenas um único encadeamento acessa o mapa por vez, mas significativamente melhor quando vários encadeamentos acessam o mapa simultaneamente.

Encontrei uma entrada de blog que reproduz uma tabela do excelente livro Java Concurrency In Practice , que eu recomendo completamente.

Collections.synchronizedMap realmente só faz sentido se você precisar agrupar um mapa com algumas outras características, talvez algum tipo de mapa ordenado, como um TreeMap.


2
Sim - parece que eu mencionei esse livro em todas as outras respostas que eu der!
Bill Michell

@BillMichell O link está quebrado

@Govinda Desative o javascript antes de acessar o link. A entrada do blog ainda está lá!
Bill Michell

32

A principal diferença entre esses dois é que ConcurrentHashMapbloqueará apenas parte dos dados que estão sendo atualizados, enquanto outras partes dos dados podem ser acessadas por outros threads. No entanto, Collections.synchronizedMap()todos os dados serão bloqueados durante a atualização; outros segmentos só poderão acessar os dados quando o bloqueio for liberado. Se houver muitas operações de atualização e uma quantidade relativamente pequena de operações de leitura, você deverá escolher ConcurrentHashMap.

Outra diferença é que ConcurrentHashMapnão preservará a ordem dos elementos no mapa transmitido. É semelhante ao HashMaparmazenamento de dados. Não há garantia de que a ordem dos elementos seja preservada. Enquanto Collections.synchronizedMap()preservará a ordem dos elementos do mapa transmitido. Por exemplo, se você passar um TreeMappara ConcurrentHashMap, a ordem dos elementos no ConcurrentHashMappode não ser a mesma que a ordem no TreeMap, mas Collections.synchronizedMap()preservará a ordem.

Além disso, ConcurrentHashMappode garantir que não haja ConcurrentModificationExceptionlançamentos enquanto um thread estiver atualizando o mapa e outro thread estiver percorrendo o iterador obtido no mapa. No entanto, Collections.synchronizedMap()não é garantido isso.

um post que demonstra as diferenças desses dois e também o ConcurrentSkipListMap.


13

Mapa sincronizado:

O Mapa Sincronizado também não é muito diferente do Hashtable e fornece desempenho semelhante em programas Java simultâneos. A única diferença entre Hashtable e SynchronizedMap é que SynchronizedMap não é um legado e você pode agrupar qualquer mapa para criar sua versão sincronizada usando o método Collections.synchronizedMap ().

ConcurrentHashMap:

A classe ConcurrentHashMap fornece uma versão simultânea do HashMap padrão. Este é um aprimoramento da funcionalidade synchronizedMap fornecida na classe Collections.

Ao contrário do Hashtable e do mapa sincronizado, ele nunca bloqueia o mapa inteiro; em vez disso, divide o mapa em segmentos e o bloqueio é feito nesses. Ele tem um desempenho melhor se o número de threads do leitor for maior que o número de threads do gravador.

ConcurrentHashMap, por padrão, é separado em 16 regiões e os bloqueios são aplicados. Esse número padrão pode ser definido ao inicializar uma instância ConcurrentHashMap. Ao definir dados em um segmento específico, a trava para esse segmento é obtida. Isso significa que duas atualizações ainda podem ser executadas simultaneamente com segurança se cada uma delas afetar depósitos separados, minimizando a contenção de bloqueios e maximizando o desempenho.

ConcurrentHashMap não lança uma ConcurrentModificationException

ConcurrentHashMap não lança uma ConcurrentModificationException se um thread tentar modificá-lo enquanto outro estiver iterando sobre ele

Diferença entre synchornizedMap e ConcurrentHashMap

Collections.synchornizedMap (HashMap) retornará uma coleção que é quase equivalente ao Hashtable, onde todas as operações de modificação no Map são bloqueadas no objeto Map enquanto no caso do ConcurrentHashMap, a segurança do thread é alcançada dividindo todo o Mapa em uma partição diferente, com base no nível de simultaneidade. e apenas bloquear parte específica em vez de bloquear todo o mapa.

ConcurrentHashMap não permite chaves nulas ou valores nulos, enquanto o HashMap sincronizado permite uma chave nula.

Links semelhantes

Link1

Link2

Comparação de desempenho


12

Como sempre, há tradeoffs de simultaneidade - sobrecarga - velocidade envolvidos. Você realmente precisa considerar os requisitos detalhados de simultaneidade do seu aplicativo para tomar uma decisão e, em seguida, testar seu código para ver se é bom o suficiente.


12

Em ConcurrentHashMap, o bloqueio é aplicado a um segmento em vez de a um mapa inteiro. Cada segmento gerencia sua própria tabela de hash interna. O bloqueio é aplicado apenas para operações de atualização. Collections.synchronizedMap(Map)sincroniza o mapa inteiro.



9

Você está certo HashTable, você pode esquecê-lo.

Seu artigo menciona o fato de que, enquanto o HashTable e a classe de wrapper sincronizado fornecem segurança básica de encadeamento, permitindo apenas um encadeamento por vez acessar o mapa, isso não é uma segurança de encadeamento 'verdadeira', pois muitas operações compostas ainda exigem sincronização adicional, por exemplo:

synchronized (records) {
  Record rec = records.get(id);
  if (rec == null) {
      rec = new Record(id);
      records.put(id, rec);
  }
  return rec;
}

No entanto, não pense que ConcurrentHashMapé uma alternativa simples para um bloco HashMaptípico synchronized, como mostrado acima. Leia este artigo para entender melhor seus meandros.


7

Aqui estão alguns:

1) ConcurrentHashMap bloqueia apenas parte do mapa, mas SynchronizedMap bloqueia o MAp inteiro.
2) ConcurrentHashMap tem melhor desempenho em relação ao SynchronizedMap e é mais escalável.
3) No caso de vários leitores e gravadores individuais, o ConcurrentHashMap é a melhor opção.

Este texto é da Diferença entre ConcurrentHashMap e hashtable em Java


7

Podemos obter segurança de encadeamento usando ConcurrentHashMap e synchronisedHashmap e Hashtable. Mas há muita diferença se você olhar para a arquitetura deles.

  1. synchronisedHashmap e Hashtable

Ambos manterão o bloqueio no nível do objeto. Portanto, se você deseja executar qualquer operação como put / get, é necessário adquirir o bloqueio primeiro. Ao mesmo tempo, outros threads não têm permissão para executar nenhuma operação. Portanto, por vez, apenas um segmento pode operar com isso. Portanto, o tempo de espera aumentará aqui. Podemos dizer que o desempenho é relativamente baixo quando você compara com o ConcurrentHashMap.

  1. ConcurrentHashMap

Manterá a trava no nível do segmento. Possui 16 segmentos e mantém o nível de simultaneidade como 16 por padrão. Assim, por vez, 16 threads podem operar no ConcurrentHashMap. Além disso, a operação de leitura não requer um bloqueio. Portanto, qualquer número de threads pode executar uma operação get nele.

Se o thread1 deseja executar a operação de put no segmento 2 e o thread2 deseja executar a operação de put no segmento 4, então é permitido aqui. Significa que 16 threads podem executar uma operação de atualização (colocar / excluir) no ConcurrentHashMap por vez.

Para que o tempo de espera seja menor aqui. Portanto, o desempenho é relativamente melhor que o hashmap e o hashtable sincronizados.


1
1. o que acontece se vários threads tentarem editar o mesmo bloco? 2. O que acontece se, digamos, dois threads tentarem ler dados do mesmo bloco em que outro thread estiver gravando dados ao mesmo tempo?
Prnjn 21/0518

6

ConcurrentHashMap

  • Você deve usar o ConcurrentHashMap quando precisar de uma concorrência muito alta no seu projeto.
  • É seguro para threads sem sincronizar o mapa inteiro.
  • As leituras podem acontecer muito rapidamente enquanto a gravação é feita com um bloqueio.
  • Não há bloqueio no nível do objeto.
  • O bloqueio tem uma granularidade muito mais fina no nível do bloco de hashmap.
  • ConcurrentHashMap não lança uma ConcurrentModificationException se um thread tentar modificá-lo enquanto outro está iterando sobre ele.
  • ConcurrentHashMap usa vários bloqueios.

SynchronizedHashMap

  • Sincronização no nível do objeto.
  • Toda operação de leitura / gravação precisa adquirir bloqueio.
  • Bloquear a coleção inteira é uma sobrecarga de desempenho.
  • Essencialmente, isso dá acesso a apenas um thread no mapa inteiro e bloqueia todos os outros threads.
  • Isso pode causar contenção.
  • SynchronizedHashMap retorna o Iterator, que falha rapidamente na modificação simultânea.

fonte


4

ConcurrentHashMap é otimizado para acesso simultâneo.

Os acessos não bloqueiam o mapa inteiro, mas usam uma estratégia mais refinada, o que melhora a escalabilidade. Também há aprimoramentos funcionais especificamente para acesso simultâneo, por exemplo, iteradores simultâneos.


4

um recurso crítico a ser observado ConcurrentHashMapalém do recurso de simultaneidade que ele fornece, que é o iterador à prova de falhas . Vi desenvolvedores usando ConcurrentHashMapapenas porque desejam editar o conjunto de entradas - colocar / remover enquanto iteram sobre ele. Collections.synchronizedMap(Map)não fornece um iterador à prova de falhas, mas sim um iterador à prova de falhas . Os iteradores fail-fast usam instantâneos do tamanho do mapa que não podem ser editados durante a iteração.


3
  1. Se a consistência dos dados for muito importante - use Hashtable ou Collections.synchronizedMap (Mapa).
  2. Se a velocidade / desempenho for altamente importante e a Atualização de dados puder ser comprometida, use o ConcurrentHashMap.

2

Em geral, se você deseja usar o, ConcurrentHashMapverifique se está pronto para perder as 'atualizações'
(ou seja, imprimir o conteúdo do HashMap não garante que ele imprima o Mapa atualizado) e use APIs como essa CyclicBarrierpara garantir a consistência entre os programas. ciclo da vida.


1

O método Collections.synchronizedMap () sincroniza todos os métodos do HashMap e o reduz efetivamente a uma estrutura de dados em que um thread pode entrar por vez, porque bloqueia todos os métodos em um bloqueio comum.

No ConcurrentHashMap, a sincronização é feita de maneira um pouco diferente. Em vez de bloquear todos os métodos em um bloqueio comum, o ConcurrentHashMap usa bloqueio separado para baldes separados, bloqueando apenas uma parte do Mapa. Por padrão, existem 16 baldes e também bloqueios separados para baldes separados. Portanto, o nível de simultaneidade padrão é 16. Isso significa que, teoricamente, a qualquer momento, 16 threads podem acessar o ConcurrentHashMap se todos eles estiverem indo para separar os buckets.


1

O ConcurrentHashMap foi apresentado como alternativa ao Hashtable no Java 1.5 como parte do pacote de simultaneidade. Com o ConcurrentHashMap, você tem uma opção melhor, não apenas se pode ser usado com segurança no ambiente multithread simultâneo, mas também oferece melhor desempenho do que o Hashtable e o synchronizedMap. ConcurrentHashMap tem melhor desempenho porque bloqueia uma parte do mapa. Ele permite operações de leitura simultâneas e, ao mesmo tempo, mantém a integridade, sincronizando as operações de gravação.

Como o ConcurrentHashMap é implementado

O ConcurrentHashMap foi desenvolvido como alternativa ao Hashtable e suporta todas as funcionalidades do Hashtable com capacidade adicional, o chamado nível de simultaneidade. ConcurrentHashMap permite que vários leitores leiam simultaneamente sem usar blocos. Torna-se possível separando o Mapa em partes diferentes e bloqueando apenas parte do Mapa nas atualizações. Por padrão, o nível de simultaneidade é 16, portanto, o Mapa é dividido em 16 partes e cada parte é gerenciada por bloco separado. Isso significa que 16 threads podem trabalhar com o Map simultaneamente, se trabalharem com diferentes partes do Map. Isso torna o ConcurrentHashMap altamente produtivo, e não diminui a segurança dos threads.

Se você está interessado em alguns recursos importantes do ConcurrentHashMap e quando deve usar essa realização do Map - apenas coloquei um link para um bom artigo - Como usar o ConcurrentHashMap em Java


0

Além do que foi sugerido, eu gostaria de postar o código fonte relacionado a SynchronizedMap .

Para tornar um Mapthread seguro, podemos usarCollections.synchronizedMap instrução e inserir a instância do mapa como parâmetro.

A implementação de synchronizedMapin Collectionsé como abaixo

   public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
        return new SynchronizedMap<>(m);
    }

Como você pode ver, o Mapobjeto de entrada é envolvido pelo SynchronizedMapobjeto.
Vamos nos aprofundar na implementação de SynchronizedMap,

 private static class SynchronizedMap<K,V>
        implements Map<K,V>, Serializable {
        private static final long serialVersionUID = 1978198479659022715L;

        private final Map<K,V> m;     // Backing Map
        final Object      mutex;        // Object on which to synchronize

        SynchronizedMap(Map<K,V> m) {
            this.m = Objects.requireNonNull(m);
            mutex = this;
        }

        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }

        public int size() {
            synchronized (mutex) {return m.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return m.isEmpty();}
        }
        public boolean containsKey(Object key) {
            synchronized (mutex) {return m.containsKey(key);}
        }
        public boolean containsValue(Object value) {
            synchronized (mutex) {return m.containsValue(value);}
        }
        public V get(Object key) {
            synchronized (mutex) {return m.get(key);}
        }

        public V put(K key, V value) {
            synchronized (mutex) {return m.put(key, value);}
        }
        public V remove(Object key) {
            synchronized (mutex) {return m.remove(key);}
        }
        public void putAll(Map<? extends K, ? extends V> map) {
            synchronized (mutex) {m.putAll(map);}
        }
        public void clear() {
            synchronized (mutex) {m.clear();}
        }

        private transient Set<K> keySet;
        private transient Set<Map.Entry<K,V>> entrySet;
        private transient Collection<V> values;

        public Set<K> keySet() {
            synchronized (mutex) {
                if (keySet==null)
                    keySet = new SynchronizedSet<>(m.keySet(), mutex);
                return keySet;
            }
        }

        public Set<Map.Entry<K,V>> entrySet() {
            synchronized (mutex) {
                if (entrySet==null)
                    entrySet = new SynchronizedSet<>(m.entrySet(), mutex);
                return entrySet;
            }
        }

        public Collection<V> values() {
            synchronized (mutex) {
                if (values==null)
                    values = new SynchronizedCollection<>(m.values(), mutex);
                return values;
            }
        }

        public boolean equals(Object o) {
            if (this == o)
                return true;
            synchronized (mutex) {return m.equals(o);}
        }
        public int hashCode() {
            synchronized (mutex) {return m.hashCode();}
        }
        public String toString() {
            synchronized (mutex) {return m.toString();}
        }

        // Override default methods in Map
        @Override
        public V getOrDefault(Object k, V defaultValue) {
            synchronized (mutex) {return m.getOrDefault(k, defaultValue);}
        }
        @Override
        public void forEach(BiConsumer<? super K, ? super V> action) {
            synchronized (mutex) {m.forEach(action);}
        }
        @Override
        public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
            synchronized (mutex) {m.replaceAll(function);}
        }
        @Override
        public V putIfAbsent(K key, V value) {
            synchronized (mutex) {return m.putIfAbsent(key, value);}
        }
        @Override
        public boolean remove(Object key, Object value) {
            synchronized (mutex) {return m.remove(key, value);}
        }
        @Override
        public boolean replace(K key, V oldValue, V newValue) {
            synchronized (mutex) {return m.replace(key, oldValue, newValue);}
        }
        @Override
        public V replace(K key, V value) {
            synchronized (mutex) {return m.replace(key, value);}
        }
        @Override
        public V computeIfAbsent(K key,
                Function<? super K, ? extends V> mappingFunction) {
            synchronized (mutex) {return m.computeIfAbsent(key, mappingFunction);}
        }
        @Override
        public V computeIfPresent(K key,
                BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.computeIfPresent(key, remappingFunction);}
        }
        @Override
        public V compute(K key,
                BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.compute(key, remappingFunction);}
        }
        @Override
        public V merge(K key, V value,
                BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.merge(key, value, remappingFunction);}
        }

        private void writeObject(ObjectOutputStream s) throws IOException {
            synchronized (mutex) {s.defaultWriteObject();}
        }
    }

O que SynchronizedMapfaz pode ser resumido como a adição de um único bloqueio ao método primário do Mapobjeto de entrada . Todo o método protegido pelo bloqueio não pode ser acessado por vários threads ao mesmo tempo. Isso significa operações normais como pute getpode ser executado por um único thread ao mesmo tempo para todos os dados no Mapobjeto.

Torna o Mapthread do objeto seguro agora, mas o desempenho pode se tornar um problema em alguns cenários.

Como ConcurrentMapé muito mais complicado na implementação, podemos consultar Construindo um HashMap melhor para obter detalhes. Em poucas palavras, é implementado levando em consideração o desempenho e a segurança do thread.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.