¿Es HashMap seguro para subprocesos para diferentes claves?

Si tengo dos hilos múltiples que acceden a un HashMap, pero les garantizo que nunca accederán a la misma llave al mismo tiempo, ¿podría eso conducir a una condición de carrera?

En la respuesta de @dotsid él dice esto:

Si cambia un HashMap de alguna manera, entonces su código simplemente se rompe.

Él está correcto. Un HashMap que se actualiza sin sincronización se romperá incluso si los hilos están utilizando conjuntos de claves disjuntos. Estas son algunas de las cosas que pueden salir mal.

  • Si un hilo hace un put , entonces otro hilo puede ver un valor obsoleto para el tamaño del hashmap.

  • Cuando un subproceso hace un put que desencadena una reconstrucción de la tabla, otro subproceso puede ver las versiones transitorias o obsoletas de la referencia de matriz hashtable, su tamaño, su contenido o las cadenas hash. Caos puede seguir.

  • Cuando un hilo hace un put para una clave que colisiona con alguna clave utilizada por algún otro hilo, y el último hilo hace una put para su clave, entonces este último puede ver una copia obsoleta de la referencia de la cadena hash. Caos puede seguir.

  • Cuando un hilo prueba la tabla con una tecla que colisiona con una de las claves de otro hilo, puede encontrar esa clave en la cadena. Llamará a iguales en esa clave, y si los hilos no están sincronizados, el método igual puede encontrar estado obsoleto en esa clave.

Y si tiene dos hilos al mismo tiempo haciendo solicitudes de put o remove , existen numerosas oportunidades para las condiciones de carrera.

Puedo pensar en tres soluciones:

  1. Use un ConcurrentHashMap .
  2. Use un HashMap regular pero sincronícelo en el exterior; por ejemplo, utilizando mutexes primitivos, objetos de Lock , etcétera.
  3. Use un HashMap diferente para cada hilo. Si los hilos realmente tienen un conjunto de claves disjuntas, entonces no debería haber necesidad (desde una perspectiva algorítmica) de que compartan un solo Mapa. De hecho, si sus algoritmos involucran a los hilos que iteran las claves, valores o entradas del mapa en algún punto, dividir el mapa individual en múltiples mapas podría dar una aceleración significativa para esa parte del procesamiento.

Solo use un ConcurrentHashMap. El ConcurrentHashMap utiliza lockings múltiples que cubren una gama de cubos hash para reducir las posibilidades de que se impugne un locking. Hay un impacto marginal en el rendimiento para adquirir un locking no impugnado.

Para responder a su pregunta original: según el javadoc, siempre que la estructura del mapa no cambie, está bien. Esto significa que no se eliminarán elementos y no se agregarán 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 simultáneamente, y al menos uno de los subprocesos modifica estructuralmente el mapa, debe estar sincronizado 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 garantiza la visibilidad. Entonces, debes estar dispuesto a aceptar la recuperación de asociaciones obsoletas ocasionalmente.

Depende de lo que quiera decir con “acceder”. Si acaba de leer, puede leer incluso las mismas claves siempre que la visibilidad de los datos garantizados por las reglas ” pase antes que “. Esto significa que HashMap no debe cambiar y todos los cambios (construcciones iniciales) deben completarse antes de que el lector comience a acceder a HashMap .

Si cambia un HashMap de alguna manera, entonces su código simplemente se rompe. @Stephen C brinda una muy buena explicación de por qué.

EDITAR: Si el primer caso es su situación real, le recomiendo que use Collections.unmodifiableMap() para asegurarse de que su HashMap nunca se modifique. Los objetos apuntados por HashMap no deberían cambiar también, por lo que puede resultarle agresivo utilizar final palabra clave final .

Y como dice @Lars Andren, ConcurrentHashMap es la mejor opción en la mayoría de los casos.

La modificación de un HashMap sin la sincronización adecuada de dos hilos puede conducir fácilmente a una condición de carrera.

  • Cuando un put() lleva a un cambio de tamaño de la tabla interna, esto lleva algo de tiempo y el otro hilo continúa escribiendo en la tabla anterior.
  • Dos put() para diferentes claves conducen a una actualización del mismo depósito si los códigos hash de las claves son iguales al tamaño de la tabla. (En realidad, la relación entre el código hash y el índice del cubo es más complicada, pero aún pueden producirse colisiones).