variable threads lock intrinsic java synchronization

threads - synchronized class in java



Bloque sincronizado Java vs. Collections.synchronizedMap (7)

¿El siguiente código está configurado para sincronizar correctamente las llamadas en synchronizedMap ?

public class MyClass { private static Map<String, List<String>> synchronizedMap = Collections.synchronizedMap(new HashMap<String, List<String>>()); public void doWork(String key) { List<String> values = null; while ((values = synchronizedMap.remove(key)) != null) { //do something with values } } public static void addToMap(String key, String value) { synchronized (synchronizedMap) { if (synchronizedMap.containsKey(key)) { synchronizedMap.get(key).add(value); } else { List<String> valuesList = new ArrayList<String>(); valuesList.add(value); synchronizedMap.put(key, valuesList); } } } }

Desde mi punto de vista, necesito el bloque sincronizado en addToMap() para evitar que otro hilo addToMap() remove() o containsKey() antes de pasar la llamada a put() pero no necesito un bloque sincronizado en doWork() porque otro el hilo no puede ingresar al bloque sincronizado en addToMap() antes de que remove() regrese porque he creado el Mapa originalmente con Collections.synchronizedMap() . ¿Es eso correcto? ¿Hay una mejor manera de hacer esto?


Consulte el Multimap Google Collections , por ejemplo, la página 28 de esta presentación .

Si no puede usar esa biblioteca por alguna razón, considere usar ConcurrentHashMap lugar de SynchronizedHashMap ; tiene un ingenioso putIfAbsent(K,V) con el que puedes agregar atómicamente la lista de elementos si no está ya allí. Además, considere usar CopyOnWriteArrayList para los valores del mapa si sus patrones de uso lo justifican.


Eso me parece correcto. Si tuviera que cambiar algo, dejaría de usar Collections.synchronizedMap () y sincronizaría todo de la misma manera, solo para hacerlo más claro.

Además, reemplazaría

if (synchronizedMap.containsKey(key)) { synchronizedMap.get(key).add(value); } else { List<String> valuesList = new ArrayList<String>(); valuesList.add(value); synchronizedMap.put(key, valuesList); }

con

List<String> valuesList = synchronziedMap.get(key); if (valuesList == null) { valuesList = new ArrayList<String>(); synchronziedMap.put(key, valuesList); } valuesList.add(value);


Existe la posibilidad de una falla sutil en su código.

[ ACTUALIZACIÓN: ya que está usando map.remove () esta descripción no es totalmente válida. Me perdí ese hecho la primera vez. :( Gracias al autor de la pregunta por señalarlo. Dejaré el resto tal como está, pero cambié el enunciado principal para decir que es potencialmente un error.]

En doWork () obtienes el valor List del mapa de una manera segura para subprocesos. Después, sin embargo, está accediendo a esa lista en un asunto inseguro. Por ejemplo, un hilo puede estar usando la lista en doWork () mientras que otro hilo invoca synchronizedMap.get (key) .add (value) en addToMap () . Esos dos accesos no están sincronizados. La regla general es que las garantías seguras para subprocesos de una colección no se extienden a las claves o valores que almacenan.

Puede solucionar esto insertando una lista sincronizada en el mapa como

List<String> valuesList = new ArrayList<String>(); valuesList.add(value); synchronizedMap.put(key, Collections.synchronizedList(valuesList)); // sync''d list

Alternativamente, puede sincronizar en el mapa mientras accede a la lista en doWork () :

public void doWork(String key) { List<String> values = null; while ((values = synchronizedMap.remove(key)) != null) { synchronized (synchronizedMap) { //do something with values } } }

La última opción limitará la concurrencia un poco, pero es algo más clara IMO.

Además, una nota rápida sobre ConcurrentHashMap. Esta es una clase realmente útil, pero no siempre es un reemplazo apropiado para HashMaps sincronizados. Citando de sus Javadocs,

Esta clase es completamente interoperable con Hashtable en programas que confían en su seguridad de subprocesos pero no en sus detalles de sincronización .

En otras palabras, putIfAbsent () es ideal para inserciones atómicas, pero no garantiza que otras partes del mapa no cambien durante esa llamada; solo garantiza la atomicidad. En su programa de ejemplo, confía en los detalles de sincronización de un HashMap (sincronizado) para cosas que no sean put () s.

Última cosa. :) Esta gran cita de Java Concurrency in Practice siempre me ayuda a diseñar un programa de depuración de subprocesos múltiples.

Para cada variable de estado mutable a la que se puede acceder mediante más de un hilo, todos los accesos a esa variable deben realizarse con el mismo bloqueo retenido.


La forma en que se sincronizó es correcta. Pero hay una trampa

  1. El contenedor sincronizado provisto por el marco Collection garantiza que las llamadas al método Ie add / get / contains se ejecutarán mutuamente excluyentes.

Sin embargo, en el mundo real generalmente consultaría el mapa antes de ingresar el valor. Por lo tanto, necesitaría hacer dos operaciones y, por lo tanto, se necesita un bloque sincronizado. Entonces la forma en que lo usaste es correcta. Sin embargo.

  1. Podría haber utilizado una implementación simultánea de Map disponible en Collection framework. El beneficio ''ConcurrentHashMap'' es

a. Tiene una API ''putIfAbsent'' que haría las mismas cosas pero de una manera más eficiente.

segundo. Es eficiente: dEl CocurrentMap simplemente bloquea las teclas, por lo tanto, no bloquea todo el mundo del mapa. Donde como ha bloqueado claves así como también valores.

do. Podría haber pasado la referencia de su objeto de mapa a otro lugar en su base de código donde usted / otro desarrollador en su tiempo puede terminar utilizándolo incorrectamente. Es decir, puede agregar () o obtener () sin bloquear el objeto del mapa. Por lo tanto, su llamada no se ejecutará de manera mutuamente exclusiva para su bloque de sincronización. Pero el uso de una implementación simultánea le da la tranquilidad de que nunca se puede usar / implementar incorrectamente.


Sí, estás sincronizando correctamente. Explicaré esto con más detalle. Debe sincronizar dos o más llamadas a métodos en el objeto synchronizedMap solo en caso de que tenga que confiar en los resultados de las llamadas al método anterior en la llamada al método posterior en la secuencia de llamadas a métodos en el objeto synchronizedMap. Echemos un vistazo a este código:

synchronized (synchronizedMap) { if (synchronizedMap.containsKey(key)) { synchronizedMap.get(key).add(value); } else { List<String> valuesList = new ArrayList<String>(); valuesList.add(value); synchronizedMap.put(key, valuesList); } }

En este código

synchronizedMap.get(key).add(value);

y

synchronizedMap.put(key, valuesList);

las llamadas a métodos se basan en el resultado de la anterior

synchronizedMap.containsKey(key)

llamada de método

Si la secuencia de llamadas a métodos no se sincronizara, el resultado podría ser incorrecto. Por ejemplo, el thread 1 está ejecutando el método addToMap() y el thread 2 está ejecutando el método doWork() La secuencia de las llamadas al método en el objeto synchronizedMap podría ser la siguiente: El Thread 1 ha ejecutado el método.

synchronizedMap.containsKey(key)

y el resultado es " true ". Después de que el sistema operativo haya cambiado el control de ejecución a la thread 2 y se haya ejecutado

synchronizedMap.remove(key)

Después de eso, el control de ejecución se ha cambiado a la thread 1 y se ha ejecutado, por ejemplo,

synchronizedMap.get(key).add(value);

creyendo que el objeto synchronizedMap contiene la key y se lanzará NullPointerException porque synchronizedMap.get(key) devolverá null . Si la secuencia de llamadas de método en el objeto synchronizedMap no depende de los resultados de los demás, no es necesario sincronizar la secuencia. Por ejemplo, no necesita sincronizar esta secuencia:

synchronizedMap.put(key1, valuesList1); synchronizedMap.put(key2, valuesList2);

aquí

synchronizedMap.put(key2, valuesList2);

método de llamada no se basa en los resultados de la anterior

synchronizedMap.put(key1, valuesList1);

Llamada a método (no importa si algún subproceso ha interferido entre las dos llamadas al método y, por ejemplo, ha eliminado la key1 ).


Si está utilizando JDK 6, entonces es posible que desee comprobar ConcurrentHashMap

Tenga en cuenta el método putIfAbsent en esa clase.


Collections.synchronizedMap() garantiza que cada operación atómica que desee ejecutar en el mapa se sincronizará.

Sin embargo, ejecutar dos (o más) operaciones en el mapa debe estar sincronizado en un bloque. Entonces sí, estás sincronizando correctamente.