Multitões seguros de thread em Java

Dado o seguinte multiton:

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

Como podemos mantê-lo seguro e preguiçoso sem a dispendiosa synchronization do método getInstance () e a controvérsia do bloqueio duplo verificado? Uma maneira eficaz de singletons é mencionada aqui, mas isso não parece se estender a multitons.

    Isso fornecerá um mecanismo de armazenamento thread-safe para seus Multitons. A única desvantagem é que é possível criar um Multiton que não será usado na chamada putIfAbsent () . A possibilidade é pequena, mas existe. Claro que na remota chance de que isso aconteça, ainda não causa nenhum dano.

    No lado positivo, não há pré-alocação ou boot necessária e nenhuma restrição de tamanho predefinida.

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

    ATUALIZAÇÃO: com o Java 8, pode ser ainda mais simples:

     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 que bom!


    RESPOSTA ORIGINAL

    Esta é uma solução que se baseia no padrão Memoizer, conforme descrito no JCiP . Ele usa um ConcurrentHashMap como uma das outras respostas, mas em vez de armazenar as instâncias do Multiton diretamente, o que pode levar à criação de instâncias não usadas, ele armazena o cálculo que leva à criação do Multiton. Essa camada adicional resolve o problema de instâncias não usadas.

     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(); } } 

    Você poderia usar uma matriz de bloqueios para, pelo menos, conseguir instâncias diferentes ao mesmo tempo:

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

    Com o advento do Java 8 e algumas melhorias no ConcurrentMap e no lambdas, agora é possível implementar um Multiton (e provavelmente até um Singleton ) de uma maneira muito mais 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; } } 

    Eu suspeito – embora isso me faça sentir desconfortável – que getInstance poderia ser ainda mais minimizado para:

     // 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()); } 

    Você está procurando por um 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; } }