Si tengo dos subprocesos múltiples accediendo a un HashMap, pero garantizo que nunca accederán a la misma clave al mismo tiempo, ¿podría eso conducir a una condición de carrera?
Si tengo dos subprocesos múltiples accediendo a un HashMap, pero garantizo que nunca accederán a la misma clave al mismo tiempo, ¿podría eso conducir a una condición de carrera?
Respuestas:
En la respuesta de @ dotsid, dice esto:
Si cambia un HashMap de alguna manera, su código simplemente está roto.
El esta en lo correcto. Un HashMap que se actualiza sin sincronización se romperá incluso si los subprocesos utilizan conjuntos de claves disjuntos. Estas son algunas de las cosas que pueden salir mal.
Si un hilo hace una put
, entonces otro hilo puede ver un valor obsoleto para el tamaño del mapa de hash.
Cuando un subproceso hace un put
que desencadena una reconstrucción de la tabla, otro subproceso puede ver versiones transitorias o obsoletas de la referencia de matriz de tabla hash, su tamaño, su contenido o las cadenas hash. Puede sobrevenir el caos.
Cuando un subproceso hace una put
clave para una clave que colisiona con alguna clave utilizada por algún otro subproceso, y el último subproceso hace una put
clave para su clave, entonces este último puede ver una copia obsoleta de la referencia de la cadena hash. Puede sobrevenir el caos.
Cuando un hilo palpa la mesa con una llave que choca con una de las llaves de algún otro hilo, puede encontrar esa llave en la cadena. Llamará a equals en esa clave, y si los subprocesos no están sincronizados, el método equals puede encontrar un estado obsoleto en esa clave.
Y si tiene dos hilos haciendo put
o remove
solicitando simultáneamente , hay numerosas oportunidades para las condiciones de carrera.
Puedo pensar en tres soluciones:
ConcurrentHashMap
.HashMap
pero sincronizado en el exterior; por ejemplo, usando mutex primitivos, Lock
objetos, etcétera.HashMap
para cada hilo. Si los subprocesos realmente tienen un conjunto de claves disjunto, entonces no debería ser necesario (desde una perspectiva algorítmica) que compartan un solo mapa. De hecho, si sus algoritmos involucran a los subprocesos que iteran las claves, valores o entradas del mapa en algún momento, dividir el mapa único en múltiples mapas podría dar una aceleración significativa para esa parte del procesamiento.Simplemente use un ConcurrentHashMap. El ConcurrentHashMap usa múltiples bloqueos que cubren un rango de cubos de hash para reducir las posibilidades de que un bloqueo sea impugnado. Existe un impacto marginal en el rendimiento al adquirir un candado sin oposición.
Para responder a su pregunta original: De acuerdo con el javadoc, mientras la estructura del mapa no cambie, está bien. Esto significa que no se eliminan elementos en absoluto y no se agregan nuevas claves que aún no están en el mapa. Reemplazar el valor asociado con las claves existentes está bien.
Si varios subprocesos acceden a un mapa hash al mismo tiempo, y al menos uno de los subprocesos modifica el mapa estructuralmente, debe sincronizarse externamente. (Una modificación estructural es cualquier operación que agrega o elimina una o más asignaciones; simplemente cambiar el valor asociado con una clave que una instancia ya contiene no es una modificación estructural).
Aunque no ofrece garantías sobre la visibilidad. Por lo tanto, debe estar dispuesto a aceptar la recuperación de asociaciones obsoletas de vez en cuando.
Depende de lo que quieras decir con "acceder". Si solo está leyendo, puede leer incluso las mismas claves siempre que la visibilidad de los datos esté garantizada bajo las reglas de " sucede antes ". Esto significa que HashMap
no debería cambiar y todos los cambios (construcciones iniciales) deberían completarse antes de que cualquier lector comience a acceder HashMap
.
Si cambia HashMap
de alguna manera, su código simplemente está roto. @ Stephen C proporciona una muy buena explicación de por qué.
EDITAR: Si el primer caso es su situación real, le recomiendo que lo use Collections.unmodifiableMap()
para asegurarse de que su HashMap nunca se cambie. Los objetos señalados por HashMap
no deben cambiar también, por lo que el uso agresivo de final
palabras clave puede ayudarlo.
Y como dice @Lars Andren, ConcurrentHashMap
es la mejor opción en la mayoría de los casos.
unmodifiableMap
asegura que el cliente no pueda cambiar el mapa. No hace nada para garantizar que el mapa subyacente no se cambie.
La modificación de un HashMap sin la sincronización adecuada de dos subprocesos puede conducir fácilmente a una condición de carrera.
put()
conduce a un cambio de tamaño de la tabla interna, esto lleva algún tiempo y el otro subproceso continúa escribiendo en la tabla anterior.put()
para diferentes claves conducen a una actualización del mismo depósito si los códigos hash de las claves son iguales en módulo al tamaño de la tabla. (En realidad, la relación entre el código hash y el índice de depósito es más complicada, pero aún pueden producirse colisiones).HashMap
implementación que esté utilizando, puede dañar las HashMap
estructuras de datos, etcétera, causadas por anomalías en la memoria.