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


607

Tengo un mapa que debe ser modificado 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, Hashtablees una implementación antigua (que amplía la Dictionaryclase obsoleta ), que se ha adaptado más tarde para adaptarse a la Mapinterfaz. Si bien está sincronizado, parece tener serios problemas de escalabilidad y se desaconseja para nuevos proyectos.

¿Pero qué hay de los otros dos? ¿Cuáles son las diferencias entre los mapas devueltos por Collections.synchronizedMap(Map)y ConcurrentHashMaps? ¿Cuál se ajusta a qué situación?


77
@SmilesinaJar El enlace está roto actualmente, aquí hay una copia archivada de este artículo: Por qué ConcurrentHashMap es mejor que Hashtable y tan bueno como un HashMap
informatik01

2
IBM: Cómo ConcurrentHashMap ofrece una mayor concurrencia sin comprometer la seguridad de los hilos @ ibm.com/developerworks/java/library/j-jtp08223/…
pramodc84

Para su información, Java 6 trajo ConcurrentSkipListMapcomo otra Mapimplementación segura para subprocesos . Diseñado para ser altamente concurrente bajo carga, utilizando el algoritmo Skip List .
Basil Bourque

Respuestas:


423

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 asegurará la coherencia (si se usa correctamente).

Utilice la segunda opción si necesita garantizar la coherencia de los datos y cada subproceso 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.


8
Mirando el código fuente, el mapa sincronizada es solamente una implementación con un mutex (bloqueo) mientras que el ConcurrentHashMap es más complejo que tratar con acceso simultáneo
Vinze

123
Tenga en cuenta también que ConcurrentHashMap no permite claves o valores nulos. Por lo tanto, NO son alternativas iguales de un mapa sincronizado.
onejigtwojig


55
@AbdullahShaikh El problema planteado en ese artículo se ha solucionado en Java 7 y se han realizado mejoras adicionales en Java 8.
pulse0ne

55
@hengxin: tan pronto como esté realizando una operación que consiste en múltiples consultas o actualizaciones del mapa o cuando esté iterando sobre el mapa, debe sincronizar manualmente en el mapa para garantizar la coherencia. Los mapas sincronizados garantizan la coherencia solo para operaciones individuales (invocaciones de métodos) en el mapa, lo que lo hace más que inútil ya que la mayoría de las operaciones de la vida real no son triviales, por lo que debe sincronizar manualmente de todos modos.
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   ║ 
╚═══════════════╩═══════════════════════════════════════╩═════════════════════╝

Respecto al mecanismo de bloqueo: Hashtable bloquea el objeto , mientras ConcurrentHashMapque solo bloquea el cubo .


13
Hashtableno está bloqueando la parte del mapa. Mira la implementación. Utiliza una synchronizedllave sin bloqueo, por lo que básicamente significa que se bloquea por completo hashtableen cada operación.
RMachnik

66
¿Qué pasa con el Mapa sincronizado?
Samuel Edwin Ward

3
El comportamiento de Collections.syncronizedMap es como el mapa de respaldo, excepto que todos los métodos son seguros para subprocesos
Sergii Shevchyk,

55
Imprimiría la tabla y la vendería por $ 5 cada una;). Good one @shevchyk
realPK

Editado: ninguno de los dos es totalmente seguro para subprocesos. Eso es un poco engañoso para los desarrolladores más nuevos. Consulte: ibm.com/developerworks/java/library/j-jtp07233/index.html para comprender que incluso ConcurrentHashMap no es completamente seguro para subprocesos de carreras de datos externas. (por ejemplo: 1 subproceso elimina un valor y otro más tarde intenta comprobar si está presente y ponerlo si no. Esa es una condición de carrera de datos y aún significa que a pesar de usar un "ConcurrentHashMap" no está aliviado de todos los problemas de seguridad del subproceso.
Zombies

142

Los "problemas de escalabilidad" Hashtableestán presentes exactamente de la misma manera 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 iterar sobre todo el Mapa, lo que puede llevar mucho tiempo para un Mapa grande, mientras que un hilo hace eso, todos los demás tienen que esperar si quieren insertar o buscar algo.

Los ConcurrentHashMapusos muy sofisticadas técnicas para reducir la necesidad de sincronización y permitir el acceso de lectura en paralelo de múltiples hilos sin sincronización y, más importante, proporciona una Iteratorque no requiere la sincronización e incluso permite que el mapa se modifica durante iteración (a pesar de que no ofrece ninguna garantía o no se devolverán los elementos que se insertaron durante la iteración).


44
¡Ahora eso es lo que quería! :) ¡El iterador no sincronizado es solo pura dulzura! Thansk por la información! :) (:
Kounavi

Gran respuesta ... pero ¿significa que durante la recuperación el hilo no recibirá las últimas actualizaciones ya que los hilos del lector no están sincronizados?
MrA

@MrA: ¿Estás preguntando sobre el ConcurrentHashMap? ¿Y qué quieres decir con "recuperación"?
Michael Borgwardt

44
@Michael Borgwardt para ConcurrentHashmap, por ejemplo. supongamos que hay múltiples hilos. algunos de ellos están actualizando el Mapa y otros obtienen datos de ese mismo mapa. Por lo tanto, en este escenario, cuando los hilos intentan leer, se garantiza que obtendrán los datos más recientes que se han actualizado, ya que los hilos lectores no tienen que contener bloqueos.
MrA

35

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

Está diseñado para escalar bien cuando es usado por múltiples hilos. El rendimiento puede ser marginalmente peor cuando solo un subproceso accede al Mapa a la vez, pero significativamente mejor cuando varios subprocesos 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 tiene sentido realmente solo si necesita envolver un mapa con otras características, tal vez algún tipo de mapa ordenado, como un TreeMap.


2
Sí, ¡parece que menciono ese libro en todas las respuestas que hago!
Bill Michell

El enlace @BillMichell está roto

@Govinda Desactive JavaScript antes de acceder al enlace. ¡La entrada del blog sigue ahí!
Bill Michell

32

La principal diferencia entre estos dos es que ConcurrentHashMapbloqueará solo una parte de los datos que se están actualizando, mientras que otros hilos pueden acceder a otra parte de los datos. Sin embargo, Collections.synchronizedMap()bloqueará todos los datos durante la actualización, otros subprocesos solo pueden acceder a los datos cuando se libera 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 ConcurrentHashMapno conservará el orden de los elementos en el mapa pasado. Es similar HashMapal almacenamiento de datos. No hay garantía de que se conserve el orden de los elementos. Mientras Collections.synchronizedMap()que preservará el orden de los elementos del Mapa pasado. Por ejemplo, si pasa un TreeMapa ConcurrentHashMap, el orden de los elementos en el ConcurrentHashMappuede no ser el mismo que el orden en el TreeMap, pero Collections.synchronizedMap()conservará el orden.

Además, ConcurrentHashMappuede garantizar que no se ConcurrentModificationExceptionarroje mientras un hilo actualiza el mapa y otro hilo atraviesa 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.


13

Mapa sincronizado:

El Mapa sincronizado tampoco es muy diferente de Hashtable y proporciona un rendimiento similar en programas Java concurrentes. La única diferencia entre Hashtable y SynchronizedMap es que SynchronizedMap no es un legado y puede ajustar cualquier Mapa para crear su versión sincronizada utilizando el método Collections.synchronizedMap ().

ConcurrentHashMap:

La clase ConcurrentHashMap proporciona una versión concurrente del HashMap estándar. Esta es una mejora en la funcionalidad sincronizada de mapas proporcionada en la clase Colecciones.

A diferencia del Mapa Hashtable y el Mapa Sincronizado, nunca bloquea todo el Mapa, sino que divide el mapa en segmentos y el bloqueo se realiza en esos. Funciona mejor si el número de hilos de lectura es mayor que el número de hilos de escritura.

ConcurrentHashMap de forma predeterminada se separa en 16 regiones y se aplican bloqueos. Este número predeterminado se puede establecer al inicializar una instancia de ConcurrentHashMap. Cuando se configuran datos en un segmento particular, se obtiene el bloqueo para ese segmento. Esto significa que dos actualizaciones aún pueden ejecutarse simultáneamente de manera segura si cada una afecta a depósitos separados, minimizando así la contención de bloqueo y maximizando así el rendimiento.

ConcurrentHashMap no produce una excepción ConcurrentModificationException

ConcurrentHashMap no arroja una ConcurrentModificationException si un hilo 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 todas las operaciones de modificación en Map están bloqueadas en el objeto Map mientras que en el caso de ConcurrentHashMap, la seguridad del subproceso se logra dividiendo Map completo en una partición diferente según el nivel de concurrencia y solo bloqueando una parte 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

Enlace1

Link2

Comparación de rendimiento


12

Como de costumbre, hay concesiones de concurrencia, gastos generales y velocidad involucrados. Realmente necesita 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.


12

En ConcurrentHashMap, el bloqueo se aplica a un segmento en lugar de 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.



9

Tienes razón HashTable, puedes olvidarte de eso.

Su artículo menciona el hecho de que, si bien HashTable y la clase de envoltura sincronizada proporcionan seguridad básica de subprocesos al permitir solo un subproceso a la vez para acceder al mapa, esto no es seguridad de subprocesos 'verdadera' ya que muchas operaciones compuestas aún requieren sincronización adicional, para 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 ConcurrentHashMapes una alternativa simple para un bloque HashMaptípico synchronizedcomo se muestra arriba. Lea este artículo para comprender mejor sus complejidades.


7

Aquí hay algunos:

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

Este texto es de Diferencia entre ConcurrentHashMap y hashtable en Java


7

Podemos lograr la seguridad del subproceso utilizando ConcurrentHashMap y sincronizedHashmap y Hashtable. Pero hay mucha diferencia si nos fijamos en su arquitectura.

  1. SynchronizedHashmap y Hashtable

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

  1. ConcurrentHashMap

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 subprocesos pueden funcionar en ConcurrentHashMap. Además, la operación de lectura no requiere un bloqueo. Por lo tanto, cualquier número de subprocesos puede realizar una operación get en él.

Si thread1 desea realizar la operación de colocación en el segmento 2 y thread2 desea realizar la operación de colocación en el segmento 4, entonces está permitido aquí. Significa que 16 subprocesos pueden realizar la operación 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 sincronizado Hashmap y Hashtable.


1
1. ¿Qué sucede si varios hilos intentan editar el mismo bloque? 2. ¿Qué sucede si digamos que dos hilos intentan leer datos del mismo bloque donde otro hilo si escribe datos al mismo tiempo?
prnjn

6

ConcurrentHashMap

  • Debe usar ConcurrentHashMap cuando necesite una concurrencia muy alta en su proyecto.
  • Es seguro para subprocesos sin sincronizar todo el mapa.
  • Las lecturas pueden ocurrir muy rápido mientras que la escritura se realiza con un candado.
  • No hay bloqueo en el nivel del objeto.
  • El bloqueo tiene una granularidad mucho más fina en un nivel de depósito de hashmap.
  • ConcurrentHashMap no arroja una ConcurrentModificationException si un hilo intenta modificarlo mientras otro está iterando sobre él.
  • ConcurrentHashMap usa multitud de bloqueos.

SynchronizedHashMap

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

fuente


4

ConcurrentHashMap está optimizado para acceso concurrente.

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


4

Hay una característica crítica a tener en cuenta ConcurrentHashMapaparte de la característica de concurrencia que proporciona, que es un iterador a prueba de fallas . He visto a los desarrolladores usar ConcurrentHashMapsolo porque quieren editar el conjunto de entrada: poner / quitar mientras itera sobre él. Collections.synchronizedMap(Map)no proporciona un iterador a prueba de fallas, pero sí proporciona un iterador rápido a prueba de fallas . Los iteradores de falla rápida utilizan una instantánea del tamaño del mapa que no se puede editar durante la iteración.


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

2

En general, si desea utilizar el, ConcurrentHashMapasegúrese de estar listo para perderse las 'actualizaciones'
(es decir, imprimir el contenido de HashMap no garantiza que imprimirá el Mapa actualizado) y utilizar API como CyclicBarrierpara garantizar la coherencia en todo el programa ciclo vital.


1

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

En ConcurrentHashMap, la sincronización se realiza de manera 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 cerraduras separadas 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 separarse.


1

ConcurrentHashMap se presentó como alternativa a Hashtable en Java 1.5 como parte del paquete de concurrencia. Con ConcurrentHashMap, tiene una mejor opción no solo si se puede usar de manera segura en el entorno concurrente de subprocesos múltiples, sino que también proporciona un mejor rendimiento que Hashtable y synchronizedMap. ConcurrentHashMap funciona mejor porque bloquea una parte de Map. Permite operaciones de lectura concurrentes y al mismo tiempo mantiene la integridad sincronizando las operaciones de escritura.

Cómo se implementa ConcurrentHashMap

ConcurrentHashMap se desarrolló como alternativa de Hashtable y admite todas las funcionalidades de Hashtable con una capacidad adicional, llamada nivel de concurrencia. ConcurrentHashMap permite que varios lectores lean simultáneamente sin usar bloques. Se hace posible separando el Mapa en diferentes partes y bloqueando solo una parte del Mapa en las actualizaciones. De forma predeterminada, el nivel de concurrencia es 16, por lo que Map se divide en 16 partes y cada parte se gestiona mediante un bloque separado. Significa que 16 hilos pueden trabajar con Map simultáneamente, si funcionan con diferentes partes de Map. Hace que ConcurrentHashMap sea muy productivo y no reduce la seguridad de los subprocesos.

Si está interesado en algunas características importantes de ConcurrentHashMap y cuándo debe usar esta realización de Map, acabo de poner un enlace a un buen artículo: Cómo usar ConcurrentHashMap en Java


0

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

Para que un Mapsubproceso sea seguro, podemos usar la Collections.synchronizedMapinstrucción e ingresar la instancia del mapa como parámetro.

La implementación de synchronizedMapen Collectionses como a continuación

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

Como puede ver, el Mapobjeto de entrada está envuelto por el SynchronizedMapobjeto.
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 SynchronizedMapque se puede resumir es agregar un solo bloqueo al método primario del Mapobjeto de entrada . Múltiples hilos al mismo tiempo no pueden acceder a todos los métodos protegidos por el bloqueo. Eso significa que las operaciones normales como puty getpueden ser ejecutadas por un solo hilo al mismo tiempo para todos los datos en el Mapobjeto.

Ahora hace que el Mapsubproceso del objeto sea seguro, pero el rendimiento puede convertirse en un problema en algunos escenarios.

La ConcurrentMapimplementación es mucho más complicada, podemos referirnos a Crear un mejor HashMap para más detalles. En pocas palabras, se implementa teniendo en cuenta tanto el subproceso seguro como el rendimiento.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.