thread safe example ejemplo concurrent java dictionary concurrency

example - java hashmap thread safe



¿Cuál es la diferencia entre ConcurrentHashMap y Collections.synchronizedMap(Mapa)? (18)

Tengo un mapa que se va a modificar por varios hilos al mismo tiempo.

Parece que hay tres implementaciones de mapas sincronizados diferentes en la API de Java:

  • Hashtable
  • Collections.synchronizedMap(Map)
  • ConcurrentHashMap

Por lo que entiendo, Hashtable es una implementación antigua (que extiende la clase de Dictionary obsoleta), que se adaptó posteriormente para que se ajuste a la interfaz del Map . Si bien está sincronizado, parece tener serios problemas de escalabilidad y no se recomienda para nuevos proyectos.

Pero ¿qué pasa con los otros dos? ¿Cuáles son las diferencias entre los mapas devueltos por Collections.synchronizedMap(Map) y ConcurrentHashMap s? ¿Cuál encaja en qué situación?


  1. Si la consistencia de los datos es muy importante, use Hashtable o Collections.synchronizedMap (Mapa).
  2. Si la velocidad / el rendimiento es muy importante y la actualización de datos puede verse comprometida, use ConcurrentHashMap.

Además de lo que se ha sugerido, me gustaría publicar el código fuente relacionado con SynchronizedMap .

Para hacer seguro un hilo de Map , podemos usar la declaración Collections.synchronizedMap e ingresar la instancia del mapa como parámetro.

La implementación de synchronizedMap en Collections es como abajo

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

Como puede ver, el objeto de Map entrada está envuelto por el objeto SynchronizedMap .
Vamos a profundizar en la implementación 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();} } }

Lo que hace SynchronizedMap puede resumirse como agregar un solo bloqueo al método primario del objeto Map entrada. No se puede acceder a todos los métodos protegidos por el bloqueo por varios subprocesos al mismo tiempo. Eso significa que las operaciones normales, como put y get pueden ser ejecutadas por un solo hilo al mismo tiempo para todos los datos en el objeto Map .

Hace que el subproceso del objeto Map ahora sea seguro, pero el rendimiento puede convertirse en un problema en algunos escenarios.

El ConcurrentMap es mucho más complicado en la implementación, podemos consultar Crear un mejor HashMap para obtener más detalles. En pocas palabras, se implementa teniendo en cuenta la seguridad y el rendimiento del subproceso.


Aquí hay algunos:

1) ConcurrentHashMap bloquea solo la parte de Map pero SynchronizedMap bloquea MAp completo.
2) ConcurrentHashMap tiene un mejor rendimiento sobre SynchronizedMap y más escalable.
3) En el caso de un lector múltiple y un solo escritor, ConcurrentHashMap es la mejor opción.

Este texto es de la diferencia entre ConcurrentHashMap y hashtable en Java


ConcurrentHashMap está optimizado para el acceso concurrente.

Los accesos no bloquean todo el mapa, sino que utilizan una estrategia más precisa, lo que mejora la escalabilidad. También hay mejoras funcionales específicas para el acceso concurrente, por ejemplo, iteradores concurrentes.


El método Collections.synchronizedMap () sincroniza todos los métodos de HashMap y lo reduce efectivamente a una estructura de datos donde un hilo puede ingresar a la vez porque bloquea todos los métodos en un bloqueo común.

En ConcurrentHashMap la sincronización se realiza de forma un poco diferente. En lugar de bloquear todos los métodos en un bloqueo común, ConcurrentHashMap usa un bloqueo separado para cubos separados, bloqueando así solo una parte del Mapa. Por defecto, hay 16 cubos y también bloqueos separados para cubos separados. Por lo tanto, el nivel de concurrencia predeterminado es 16. Eso significa que, en teoría, en cualquier momento dado, 16 subprocesos pueden acceder a ConcurrentHashMap si todos van a separar las categorías.


En ConcurrentHashMap , el bloqueo se aplica a un segmento en lugar de a un Mapa completo. Cada segmento gestiona su propia tabla hash interna. El bloqueo se aplica solo para las operaciones de actualización. Collections.synchronizedMap(Map) sincroniza todo el mapa.


En general, si desea utilizar el ConcurrentHashMap asegúrese de estar listo para omitir las ''actualizaciones''
(es decir, la impresión de contenidos de HashMap no garantiza que imprimirá el Mapa actualizado) y que use API como CyclicBarrier para garantizar la coherencia en todo el ciclo de vida de su programa.


Hay una característica crítica a tener en cuenta acerca de ConcurrentHashMap además de la característica de concurrencia que proporciona, que es un iterador a prueba de fallas . He visto desarrolladores que usan ConcurrentHashMap solo porque quieren editar el conjunto de entrada: poner / quitar mientras se iteran sobre él. Collections.synchronizedMap(Map) no proporciona un iterador a prueba de fallas, sino que proporciona un iterador a prueba de fallas en su lugar. los iteradores a prueba de fallos utilizan una instantánea del tamaño del mapa que no se puede editar durante la iteración.


La principal diferencia entre estos dos es que ConcurrentHashMap bloqueará solo una parte de los datos que se están actualizando, mientras que otros subprocesos pueden acceder a otra parte de los datos. Sin embargo, Collections.synchronizedMap() bloqueará todos los datos durante la actualización, otros subprocesos solo podrán acceder a los datos cuando se libere el bloqueo. Si hay muchas operaciones de actualización y una cantidad relativamente pequeña de operaciones de lectura, debe elegir ConcurrentHashMap .

También otra diferencia es que ConcurrentHashMap no conservará el orden de los elementos en el Mapa pasado. Es similar a HashMap cuando almacena datos. No hay garantía de que el orden del elemento se conserve. Si bien Collections.synchronizedMap() conservará el orden de los elementos del Mapa pasado. Por ejemplo, si pasa un TreeMap a ConcurrentHashMap , el orden de los elementos en el ConcurrentHashMap puede no ser el mismo que el orden en el TreeMap , pero Collections.synchronizedMap() conservará el orden.

Además, ConcurrentHashMap puede garantizar que no se ConcurrentModificationException mientras un hilo está actualizando el mapa y otro está atravesando el iterador obtenido del mapa. Sin embargo, Collections.synchronizedMap() no está garantizado en esto.

Hay una publicación que demuestra las diferencias de estos dos y también el ConcurrentSkipListMap .


Los "problemas de escalabilidad" para Hashtable están presentes exactamente de la misma forma que en Collections.synchronizedMap(Map) : usan una sincronización muy simple, lo que significa que solo un hilo puede acceder al mapa al mismo tiempo.

Esto no es un gran problema cuando tiene inserciones y búsquedas simples (a menos que lo haga con mucha intensidad), pero se convierte en un gran problema cuando necesita recorrer todo el Mapa, lo que puede llevar mucho tiempo para un Mapa grande, mientras Un hilo hace eso, todos los demás tienen que esperar si quieren insertar o buscar algo.

El ConcurrentHashMap utiliza técnicas muy sofisticadas para reducir la necesidad de sincronización y permitir el acceso de lectura en paralelo por varios subprocesos sin sincronización y, lo que es más importante, proporciona un Iterator que no requiere sincronización e incluso permite que el mapa se modifique durante la interacción (aunque no ofrece garantías si se devolverán o no los elementos que se insertaron durante la iteración).


Para sus necesidades, use ConcurrentHashMap . Permite la modificación concurrente del Mapa desde varios hilos sin la necesidad de bloquearlos. Collections.synchronizedMap(map) crea un Mapa de bloqueo que degradará el rendimiento, aunque garantiza la coherencia (si se usa correctamente).

Use la segunda opción si necesita asegurar la consistencia de los datos, y cada hilo debe tener una vista actualizada del mapa. Use el primero si el rendimiento es crítico, y cada hilo solo inserta datos en el mapa, con lecturas que ocurren con menos frecuencia.


Podemos lograr la seguridad de subprocesos utilizando ConcurrentHashMap y synchronisedHashmap and Hashtable. Pero hay mucha diferencia si miras su arquitectura.

  1. Hashmap sincronizado y hashtable

Ambos mantendrán el bloqueo en el nivel del objeto. Entonces, si desea realizar cualquier operación como poner / obtener, primero debe adquirir el bloqueo. Al mismo tiempo, otros hilos no tienen permitido realizar ninguna operación. Así que a la vez, solo un hilo puede operar en esto. Así que el tiempo de espera aumentará aquí. Podemos decir que el rendimiento es relativamente bajo cuando se compara con ConcurrentHashMap.

  1. HashMap concurrente

Mantendrá el bloqueo a nivel de segmento. Tiene 16 segmentos y mantiene el nivel de concurrencia como 16 por defecto. Entonces, a la vez, 16 hilos pueden funcionar en ConcurrentHashMap. Por otra parte, la operación de lectura no requiere un bloqueo. Así que cualquier cantidad de hilos puede realizar una operación de obtención en él.

Si el hilo 1 quiere realizar una operación de venta en el segmento 2 y el hilo 2 quiere realizar una operación de venta en el segmento 4, entonces se permite aquí. Significa que 16 hilos pueden realizar operaciones de actualización (poner / eliminar) en ConcurrentHashMap a la vez.

Para que el tiempo de espera sea menor aquí. Por lo tanto, el rendimiento es relativamente mejor que el mapa sincronizado de Hash y Hashtable.


Se prefiere ConcurrentHashMap cuando se puede usar, aunque requiere al menos Java 5.

Está diseñado para escalar bien cuando es usado por múltiples hilos. El rendimiento puede ser marginalmente más bajo cuando solo un solo hilo accede al Mapa a la vez, pero es significativamente mejor cuando múltiples hilos acceden al mapa simultáneamente.

Encontré una entrada de blog que reproduce una tabla del excelente libro Java Concurrency In Practice , que recomiendo ampliamente.

Collections.synchronizedMap solo tiene sentido si necesita envolver un mapa con algunas otras características, tal vez algún tipo de mapa ordenado, como un TreeMap.


Tienes razón sobre HashTable , puedes olvidarlo.

Su artículo menciona el hecho de que mientras HashTable y la clase de envoltura sincronizada brindan seguridad de subprocesos básica al permitir que solo un subproceso a la vez accedan al mapa, esto no es "seguro" de subprocesos ya que muchas operaciones compuestas aún requieren sincronización adicional, por ejemplo. ejemplo:

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

Sin embargo, no piense que ConcurrentHashMap es una alternativa simple para un HashMap con un bloque synchronized típico como se muestra arriba. Lee this artículo para entender mejor sus complejidades.


HashMap concurrente

  • Debe usar ConcurrentHashMap cuando necesite una concurrencia muy alta en su proyecto.
  • Es seguro para subprocesos sin sincronizar todo el mapa.
  • Las lecturas pueden suceder muy rápido, mientras que la escritura se realiza con un bloqueo.
  • No hay bloqueo en el nivel del objeto.
  • El bloqueo se encuentra en una granularidad mucho más fina a nivel de cubo hashmap.
  • ConcurrentHashMap no lanza una ConcurrentModificationException si un subproceso intenta modificarlo mientras otro está iterando sobre él.
  • ConcurrentHashMap utiliza multitud de bloqueos.

SynchronizedHashMap

  • Sincronización a nivel de objeto.
  • Cada operación de lectura / escritura necesita adquirir bloqueo.
  • Bloquear toda la colección es una sobrecarga de rendimiento.
  • Básicamente, esto da acceso a un solo hilo a todo el mapa y bloquea todos los otros hilos.
  • Puede causar contención.
  • SynchronizedHashMap devuelve Iterator, que falla rápidamente en la modificación concurrente.

source


Mapa sincronizado:

El mapa sincronizado tampoco es muy diferente de Hashtable y proporciona un rendimiento similar en programas Java simultáneos. La única diferencia entre Hashtable y SynchronizedMap es que SynchronizedMap no es un legado y puede envolver cualquier Map para crear su versión sincronizada usando el método Collections.synchronizedMap ().

HashMap concurrente:

La clase ConcurrentHashMap proporciona una versión concurrente del HashMap estándar. Esto es una mejora en la funcionalidad synchronizedMap proporcionada en la clase Colecciones.

A diferencia de Hashtable y Synchronized Map, nunca bloquea todo el mapa, sino que divide el mapa en segmentos y el bloqueo se realiza en ellos. Se realiza mejor si el número de subprocesos del lector es mayor que el número de subprocesos de escritura.

ConcurrentHashMap se divide de forma predeterminada en 16 regiones y se aplican los bloqueos. Este número predeterminado se puede establecer al inicializar una instancia de ConcurrentHashMap. Al configurar los datos en un segmento en particular, se obtiene el bloqueo para ese segmento. Esto significa que dos actualizaciones pueden ejecutarse de manera simultánea y segura si cada una afecta a grupos separados, lo que minimiza la contención de bloqueo y maximiza el rendimiento.

ConcurrentHashMap no lanza una excepción concurrenteModificationException

ConcurrentHashMap no lanza una excepción ConcurrentModificationException si un subproceso intenta modificarlo mientras otro está iterando sobre él

Diferencia entre SynchornizedMap y ConcurrentHashMap

Collections.synchornizedMap (HashMap) devolverá una colección que es casi equivalente a Hashtable, donde cada operación de modificación en el Mapa se bloquea en el objeto Map, mientras que en el caso de ConcurrentHashMap, la seguridad de subprocesos se logra dividiendo el mapa completo en una partición diferente según el nivel de concurrencia y solo bloquear una parte en particular en lugar de bloquear todo el Mapa.

ConcurrentHashMap no permite claves nulas o valores nulos, mientras que HashMap sincronizado permite claves nulas.

Enlaces similares

Link1

Link2

Comparación de rendimiento


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

Respecto al mecanismo de bloqueo: Hashtable bloquea el objeto , mientras que ConcurrentHashMap bloquea solo el contenedor .


Como de costumbre, hay concurrencia - sobrecarga - compensaciones de velocidad involucradas. Realmente debe considerar los requisitos detallados de concurrencia de su aplicación para tomar una decisión y luego probar su código para ver si es lo suficientemente bueno.