2011-03-15 10 views
18

Tengo un caché que implementé usando un HashMap de simeple. como -Asignación de referencia atómica multihilo de Java

HashMap<String,String> cache = new HashMap<String,String>(); 

Este caché se usa la mayor parte del tiempo para leer los valores del mismo. Tengo otro método que recarga el caché y, dentro de este método, básicamente creo un nuevo caché y luego le asigno la referencia. Como entiendo, la asignación de referencia de objeto es Atómica en Java.

public class myClass { 
    private HashMap<String,String> cache = null; 
    public void init() { 
     refreshCache(); 
    } 
    // this method can be called occasionally to update the cache. 
    public void refreshCache() { 
     HashMap<String,String> newcache = new HashMap<String,String>(); 
     // code to fill up the new cache 
     // and then finally 
     cache = newcache; //assign the old cache to the new one in Atomic way 
    } 
} 

Entiendo que si no declaro caché tan volátil, otros hilos no serán capaces de ver los cambios, pero no es momento crítico para mi caso de uso para propagar el cambio en la caché de otros hilos y se puede seguir trabajando con la memoria caché anterior durante un tiempo prolongado.

¿Ve algún problema de enhebrado? Tenga en cuenta que muchos subprocesos están leyendo desde la memoria caché y solo a veces la memoria caché se vuelve a cargar.

EDIT Mi principal confusión es que no tengo que usar AtomicReference aquí ya que la operación de asignación en sí es atómica?

EDITAR - Entiendo que para hacer el pedido correcto, debería marcar el caché como volátil. Pero si el método refreshCache está marcado como sincronizado, no es necesario que la memoria caché sea volátil, ya que el bloque sincronizado se ocupará tanto de ordenar como de la visibilidad.

+0

para un simple uso del almacenamiento en caché, generalmente uso un * ConcurrentHashMap * y no me preocupo por el bloqueo. Normalmente no me importa si un cálculo (referencialmente transparente) ocurre dos veces (digamos porque el hilo B comienza a calcular el mismo valor que el hilo A está actualmente calculando y, por lo tanto, aún no ha puesto en el caché). En tu caso, también haría que * cache * sea * volátil *. Pero no veo el sentido de "actualizar el caché". Convierta su caché en una LRU/MRU y simplemente agregue un nuevo valor almacenado en caché en lugar de "restablecerlo". – SyntaxT3rr0r

+0

Al menos refrescar la caché usando un nuevo mapa permite el uso de una implementación de mapa de caché sin bloqueo – ThomasRS

Respuesta

27

Es no seguro sin una barrera de memoria adecuada.

Uno pensaría que la asignación de caché (caché = newCache) ocurriría después de los pasos para llenar el caché. Sin embargo, otros subprocesos pueden sufrir un reordenamiento de estas instrucciones para que la asignación parezca ocurrir antes de llenar el caché. Por lo tanto, es posible tomar la nueva memoria caché antes de que esté completamente construida o, incluso peor, ver una ConcurrentModificationException.

Necesita forzar la relación de pasar antes para evitar este reordenamiento, y declarar la caché como volátil lo lograría.

+1

Este es un gran consejo. Tenía esta pregunta en mente y gracias por despejarla. Si el pedido se modifica, entonces es un desastre seguro. – Shamik

+0

¿Qué sucede si realizo refreshCache, un método sincronizado? – Shamik

+0

Es posible que desee sincronizarlo si desea asegurarse de que solo un hilo cambie los datos en un momento dado. Sin embargo, supongo que los hilos del lector no participarán en el bloqueo (¿pasan por un método diferente para leer el valor?), En cuyo caso la sincronización sola no le dará la protección. Hacer que el campo de caché sea volátil es una forma eficiente de lograr la relación de pasar antes. – sjlee

-1

Parece que está bien. Asegúrese de que refreshCache no se llame con demasiada frecuencia o marque como synchronized.

4

Debe marcar la memoria caché como volatile.

Si bien observa que otros subprocesos pueden continuar utilizando un caché obsoleto durante "mucho tiempo", debe tener en cuenta que, sin un borde de sincronización, es probable que continúen utilizando el caché obsoleto para siempre. Probablemente ese no sea el comportamiento deseado.

En orden de preferencia (sobre todo debido a la legibilidad):

  • Actualizar el campo en un método sincronizado
  • Uso AtomicReference<Map<>>
  • Uso volatile

Ver también este question.

0

¿Qué pasa con CopyOnWrite ..colecciones:

java.util.concurrent.CopyOnWriteArraySet 
java.util.concurrent.CopyOnWriteArraySet 
and org.apache.mina.util.CopyOnWriteMap 

Pueden ser una buena combinación en su caso y son seguros para subprocesos.

Cuestiones relacionadas