Estoy un poco confundido con respecto a un patrón que he visto en algunos de nuestros códigos heredados.

El controlador usa un mapa como caché, con un enfoque que debería ser seguro para subprocesos, sin embargo, todavía no estoy seguro de que lo sea. Tenemos un mapa, que se sincroniza correctamente durante la adición y recuperación, sin embargo, hay un poco de lógica fuera del bloque sincronizado, que hace un filtrado adicional. ( Nunca se accede al mapa en sí y a las listas fuera de este método , por lo que la modificación simultánea no es un problema; el mapa contiene algunos parámetros estables, que básicamente nunca cambian, pero se usan con frecuencia).

El código se parece al siguiente ejemplo:

public class FooBarController { 

    private final Map<String, List<FooBar>> fooBarMap = 
                    new HashMap<String, List<FooBar>>();

    public FooBar getFooBar(String key, String foo, String bar) {

        List<FooBar> foobarList;

        synchronized (fooBarMap) {
            if (fooBarMap.get(key) == null) {
                foobarList = queryDbByKey(key);
                fooBarMap.put(key, foobarList);

            } else {
                foobarList = fooBarMap.get(key);
            }
        }

        for(FooBar fooBar : foobarList) {
            if(foo.equals(fooBar.getFoo()) && bar.equals(fooBar.getBar()))
                return fooBar;
        }

        return null;
    }

    private List<FooBar> queryDbByKey(String key) {

        // ... (simple Hibernate-query) 
    } 

    // ... 
}

Según lo que sé sobre el modelo de memoria JVM, esto debería estar bien, ya que si un subproceso llena una lista, otro solo puede recuperarlo del mapa con la sincronización adecuada en su lugar, asegurando que las entradas de la lista sean visibles. (poner la lista sucede-antes obtenerla)

Sin embargo, seguimos viendo casos en los que no se encuentra una entrada que se espera que esté en el mapa, combinados con los típicos síntomas notorios de problemas de concurrencia (por ejemplo, fallas intermitentes en la producción, que no puedo reproducir en mi entorno de desarrollo; diferentes subprocesos pueden recuperar correctamente el valor, etc.)

Me pregunto si iterar a través de los elementos de la Lista como este es seguro para subprocesos.

2
Peter G. Horvath 29 ene. 2016 a las 14:09

4 respuestas

La mejor respuesta

El código que proporcionó es correcto en términos de simultaneidad. Aquí están las garantías:

  • solo un hilo a la vez agrega valores al mapa, debido a la sincronización en el objeto del mapa
  • los valores agregados por hilo se vuelven visibles para todos los demás hilos, que ingresan al bloque sincronizado

Dado eso, puede estar seguro de que todos los subprocesos que iteran en una lista ven los mismos elementos. Los problemas que describió son realmente extraños, pero dudo que estén relacionados con el código que proporcionó.

2
AdamSkywalker 29 ene. 2016 a las 11:35

En el bucle for, está devolviendo una referencia a los objetos fooBar en foobarList.

Entonces, el método que llama a getFooBar () tiene acceso al mapa a través de este objeto de referencia fooBar.

Intenta clonar fooBar antes de regresar de getFooBar ()

0
Phanindra Gopishetty 29 ene. 2016 a las 12:07
  1. En una situación como esta, la mejor opción es usar ConcurrentHashMap.
  2. Verifique si todas las Actualización-Lectura están en orden.
  3. Como entendí de tu pregunta. Hay un conjunto fijo de params que nunca cambia. Una de las formas que preferí en una situación como esta es:

    I. Para crear el map cache durante el inicio y conservar solo una instancia de él.

    II. Lea la map Instance en cualquier momento en cualquier lugar de la aplicación.

0
Helios 29 ene. 2016 a las 11:28

Podría ser seguro para subprocesos solo si todos los accesos también fooBarMap están sincronizados. Un poco fuera de alcance, pero puede ser más seguro usar un ConcurrentHashmap.

Hay un gran artículo sobre cómo se pueden sincronizar los hashmaps aquí.

0
DominicEU 29 ene. 2016 a las 11:20