2012-06-20 12 views
11

Dada la siguiente Multiton:rosca multitons seguras en Java

public class Multiton 
{ 
    private static final Multiton[] instances = new Multiton[...]; 

    private Multiton(...) 
    { 
     //... 
    } 

    public static Multiton getInstance(int which) 
    { 
     if(instances[which] == null) 
     { 
      instances[which] = new Multiton(...); 
     } 

     return instances[which]; 
    } 
} 

¿Cómo podemos mantenerlo hilo de seguridad y perezoso y sin la sincronización costosa del método getInstance() y la polémica de bloqueo doble comprobación? Se menciona una forma efectiva para singletons here pero eso no parece extenderse a multitonos.

+3

Usted sabe que oí º Esta técnica se llama nunca usar el estado mutable global (excepto cuando los frameworks lo fuerzan a hacerlo). –

+5

¿Tiene alguna evidencia de que la sincronización "costosa" (pero realmente fácil de hacer) del método 'getInstance' realmente sería significativa en su aplicación? –

+0

No, esto es principalmente un ejercicio académico. Dado que la sincronización de Singleton ya parece ser un tema debatido, me preguntaba si la solución interna para Singletons se extiende de alguna manera a Multitons. – donnyton

Respuesta

5

Esto le dará proporcionar un mecanismo de almacenamiento multi-hilo para sus Multitons. El único inconveniente es que es posible crear una Multiton que no se utilizará en la llamada putIfAbsent(). La posibilidad es pequeña pero existe. Por supuesto, en la remota posibilidad de que suceda, todavía no causa ningún daño.

En el lado positivo, no se requiere preasignación ni inicialización ni restricciones de tamaño predefinidas.

private static ConcurrentHashMap<Integer, Multiton> instances = new ConcurrentHashMap<Integer, Multiton>(); 

public static Multiton getInstance(int which) 
{ 
    Multiton result = instances.get(which); 

    if (result == null) 
    { 
     Multiton m = new Multiton(...); 
     result = instances.putIfAbsent(which, m); 

     if (result == null) 
      result = m; 
    } 

    return result; 
} 
+0

'putIfAbsent' devuelve' null' si no hubo una asignación para la clave (consulte el [doc] (http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ConcurrentHashMap.html)). Por lo tanto, 'result' seguirá siendo nulo la primera vez que se llame a' getInstance'. – sp00m

+1

Buena captura, actualicé la muestra del código para reflejar eso. – Robin

4

Se podría utilizar un conjunto de cerraduras, por lo menos a ser capaz de obtener diferentes instancias simultáneamente:

private static final Multiton[] instances = new Multiton[...]; 
private static final Object[] locks = new Object[instances.length]; 

static { 
    for (int i = 0; i < locks.length; i++) { 
     locks[i] = new Object(); 
    } 
} 

private Multiton(...) { 
    //... 
} 

public static Multiton getInstance(int which) { 
    synchronized(locks[which]) { 
     if(instances[which] == null) { 
      instances[which] = new Multiton(...); 
     } 
     return instances[which]; 
    } 
} 
+0

Solo tenga en cuenta que es * mucho * más difícil escribir la versión no bloqueante para esto, debido a la semántica de matriz volátil. – Voo

+0

Esto le permitirá obtener diferentes instancias al mismo tiempo, pero ¿hay alguna solución a la sincronización innecesaria alrededor de las instancias individuales? Parece que volvemos al dilema de bloqueo comprobado en este punto. – donnyton

+2

La solución más simple es iniciar ansiosamente todas las instancias. Esta es la solución más simple, y es la mejor en muchos casos. Si la pereza es un requisito absoluto, primero asegúrese de que la sincronización simple constituya un problema de rendimiento antes de tratar de optimizarla. La sincronización es rápida, o al menos mucho más rápida que IO, acceso a la base de datos, llamadas entre procesos, algoritmos O (n^2), etc. No preoptimize. Es la raíz de todo mal. –

2

Está buscando AtomicReferenceArray.

public class Multiton { 
    private static final AtomicReferenceArray<Multiton> instances = new AtomicReferenceArray<Multiton>(1000); 

    private Multiton() { 
    } 

    public static Multiton getInstance(int which) { 
    // One there already? 
    Multiton it = instances.get(which); 
    if (it == null) { 
     // Lazy make. 
     Multiton newIt = new Multiton(); 
     // Successful put? 
     if (instances.compareAndSet(which, null, newIt)) { 
     // Yes! 
     it = newIt; 
     } else { 
     // One appeared as if by magic (another thread got there first). 
     it = instances.get(which); 
     } 
    } 

    return it; 
    } 
} 
+0

El problema con esta solución es que puede terminar llamando al constructor de Multiton varias veces para la misma clave. Si se ejecuta el bloque else, entonces newIt se descarta porque otro hilo también creó el objeto. –

+1

@BryanRink - Tiene toda la razón. Hay uno mejor aquí [¿Es esto un Multitón verdadero y es seguro para subprocesos] (http://codereview.stackexchange.com/q/39965/11913) y uno de Java 8 [aquí] (http: // stackoverflow. com/a/20331869/823393) – OldCurmudgeon

15

ACTUALIZACIÓN: con Java 8, puede ser aún más simple:

public class Multiton { 
    private static final ConcurrentMap<String, Multiton> multitons = new ConcurrentHashMap<>(); 

    private final String key; 
    private Multiton(String key) { this.key = key; } 

    public static Multiton getInstance(final String key) { 
     return multitons.computeIfAbsent(key, Multiton::new); 
    } 
} 

Mmm eso es bueno!


respuesta original

Esta es una solución que se basa en the Memoizer pattern as described in JCiP. Utiliza un ConcurrentHashMap como una de las otras respuestas, pero en lugar de almacenar las instancias de Multiton directamente, lo que puede conducir a la creación de instancias no utilizadas, almacena el cálculo que conduce a la creación de Multiton. Esa capa adicional resuelve el problema de las instancias no utilizadas.

public class Multiton { 

    private static final ConcurrentMap<Integer, Future<Multiton>> multitons = new ConcurrentHashMap<>(); 
    private static final Callable<Multiton> creator = new Callable<Multiton>() { 
     public Multiton call() { return new Multiton(); } 
    }; 

    private Multiton(Strnig key) {} 

    public static Multiton getInstance(final Integer key) throws InterruptedException, ExecutionException { 
     Future<Multiton> f = multitons.get(key); 
     if (f == null) { 
      FutureTask<Multiton> ft = new FutureTask<>(creator); 
      f = multitons.putIfAbsent(key, ft); 
      if (f == null) { 
       f = ft; 
       ft.run(); 
      } 
     } 
     return f.get(); 
    } 
} 
+0

¡Te gusta esto! ¿Por qué el 'while (true)'? – OldCurmudgeon

+0

En la implementación original, el cliente puede cancelar la tarea, en cuyo caso tiene sentido (si dos hilos llaman a getInstance en la misma clave y uno cancela la tarea que no desea penalizar a la otra). En mi ejemplo, es redundante porque no puede suceder. Lo limpiaré. – assylias

+0

¿'FutureTask' funciona de manera predecible si se llama' get' antes de 'ejecutar'? – OldCurmudgeon

3

Con el advenimiento de Java 8 y algunas mejoras en ConcurrentMap y lambdas, ahora es posible implementar un Multiton (y probablemente incluso un Singleton) de una manera mucho más ordenada:

public class Multiton { 
    // Map from the index to the item. 
    private static final ConcurrentMap<Integer, Multiton> multitons = new ConcurrentHashMap<>(); 

    private Multiton() { 
    // Possibly heavy construction. 
    } 

    // Get the instance associated with the specified key. 
    public static Multiton getInstance(final Integer key) throws InterruptedException, ExecutionException { 
    // Already made? 
    Multiton m = multitons.get(key); 
    if (m == null) { 
     // Put it in - only create if still necessary. 
     m = multitons.computeIfAbsent(key, k -> new Multiton()); 
    } 
    return m; 
    } 
} 

Sospecho - a pesar de que me haría sentir incómoda - que getInstance podrían minimizarse aún más a:

// Get the instance associated with the specified key. 
public static Multiton getInstance(final Integer key) throws InterruptedException, ExecutionException { 
    // Put it in - only create if still necessary. 
    return multitons.computeIfAbsent(key, k -> new Multiton()); 
} 
+0

¿Por qué te hace sentir incómodo? – assylias

+0

@assylias: porque todavía tengo que entender el concepto de pasar parámetros a métodos que solo se ejecutan potencialmente. Ese 'nuevo Multiton()' me mira y el hábito dice * que se está creando un objeto *. Sé que es irracional, solo tengo que lidiar con eso. – OldCurmudgeon