2010-04-22 17 views

Respuesta

72

En respuesta @ de dotsid dice esto:

Si cambia un HashMap de algún modo, tu código simplemente está roto.

Él es correcto.Un HashMap que se actualiza sin sincronización 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, a continuación, otro hilo puede ver un valor rancio para el tamaño del mapa hash.

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

  • Cuando un hilo hace un put para una llave que choca con alguna clave utilizada por algún otro hilo, y el último hilo hace un put por su clave, entonces el último podría ver una copia rancio de referencia cadena hash. Caos puede seguir.

  • Cuando un hilo explora 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 simultáneamente haciendo put o remove solicitudes, hay numerosas oportunidades para las condiciones de carrera.

puedo pensar en tres soluciones:

  1. Utilice un ConcurrentHashMap.
  2. Use un HashMap regular, pero sincronícelo en el exterior; p.ej. utilizando mutexes primitivos, Lock objetos, etcétera.
  3. Utilice 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.
+2

4. Utilice un HashMap regular y use un ReentrantReadWriteLock para permitir múltiples concurrentes lectores, pero exclusividad para escribir operaciones. –

+2

Subcase de 2. :-) No creo que haya ninguna necesidad de enumerar las diversas formas en que uno puede sincronizar el acceso a un HashMap regular. –

+0

¿Se puede usar ** el bloque ** sincronizado para esto? –

5

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

Si cambia un HashMap de alguna manera, 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 agresivo al usar la palabra clave final puede ayudarlo.

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

+0

¿Qué tal ConcurrentHashMap? –

+2

ConcurrentHashMap es la mejor opción en mi opinión. La única razón por la que no lo recomendé, debido a que el autor no lo preguntó :) Tiene menos rendimiento debido a las operaciones de CAS, pero como la regla de oro de la programación simultánea dice: "Hacer las cosas bien, y solo entonces hacer es rápido ":) –

+0

' no modificableMapa' asegura que el cliente no puede cambiar el mapa. No hace nada para garantizar que el mapa subyacente no se modifique. –

24

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

Para responder a su pregunta original: Según el javadoc, siempre que la estructura del mapa no cambie, usted 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 simultáneamente a un mapa hash y al menos uno de los subprocesos modifica estructuralmente el mapa, debe estar sincronizado externamente. (Una modificación estructural es cualquier operación que añade o elimina una o más asignaciones; cambiando simplemente el valor asociado con una clave que una instancia ya contiene no es una modificación estructural.)

A pesar de que no ofrece ninguna garantía sobre la visibilidad. Entonces, debes estar dispuesto a aceptar la recuperación de asociaciones obsoletas ocasionalmente.

+0

+1 para la precisión técnica (si no es la mejor práctica :-) – finnw

3

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

  • Cuando un put() conduce a un cambio de tamaño de la tabla interna, esto lleva un tiempo y el otro hilo continúa escribiendo en la mesa de edad.
  • Dos put() para diferentes claves dan lugar a una actualización del mismo depósito si las claves de las claves son iguales al tamaño de la tabla. (En realidad, la relación entre el código hash y el índice de cubo es más complicado, pero todavía se puede producir colisiones.)
Cuestiones relacionadas