Enlazar multitonos seguros en Java

Dado el siguiente multitón:

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 seguro y flojo sin la costosa sincronización del método getInstance () y la controversia del locking verificado por duplicado? Aquí se menciona una forma efectiva para los singleton , pero eso no parece extenderse a los multitones.

Esto le proporcionará un mecanismo de almacenamiento seguro para sus Multitonos. 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 o inicialización ni restricciones de tamaño predefinidas.

 private static ConcurrentHashMap instances = new ConcurrentHashMap(); 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; } 

ACTUALIZACIÓN: con Java 8, puede ser incluso más simple:

 public class Multiton { private static final ConcurrentMap 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 el patrón Memoizer como se describe en 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> multitons = new ConcurrentHashMap<>(); private static final Callable creator = new Callable() { public Multiton call() { return new Multiton(); } }; private Multiton(Strnig key) {} public static Multiton getInstance(final Integer key) throws InterruptedException, ExecutionException { Future f = multitons.get(key); if (f == null) { FutureTask ft = new FutureTask<>(creator); f = multitons.putIfAbsent(key, ft); if (f == null) { f = ft; ft.run(); } } return f.get(); } } 

Puede usar una matriz de lockings para, al menos, poder obtener instancias diferentes al mismo tiempo:

 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]; } } 

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 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, aunque me haría sentir incómodo, que getInstance podría reducirse 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()); } 

Está buscando un AtomicReferenceArray .

 public class Multiton { private static final AtomicReferenceArray instances = new AtomicReferenceArray(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; } }