13

Given the following 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];
    }
}

How can we keep it thread safe and lazy without the expensive synchronization of the getInstance() method and the controversy of double-checked locking? An effective way for singletons is mentioned here but that doesn't seem to extend to multitons.

Community
  • 1
  • 1
donnyton
  • 5,874
  • 9
  • 42
  • 60
  • 3
    You know I heard there is this technique called never using global mutable state (except when frameworks force you to). – L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳ Jun 20 '12 at 19:37
  • 5
    Do you have any evidence that the "expensive" (but really simple to get right) synchronization of the `getInstance` method would actually be significant in your application? – Jon Skeet Jun 20 '12 at 19:43
  • No, this is primarily an academic exercise. Since Singleton synchronization already seems to be a debated topic, I was wondering if the internal-class workaround for Singletons extends in any way to Multitons. – donnyton Jun 20 '12 at 21:04

5 Answers5

18

UPDATE: with Java 8, it can be even simpler:

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 that's good!


ORIGINAL ANSWER

This is a solution which builds on the Memoizer pattern as described in JCiP. It uses a ConcurrentHashMap like one of the other answers, but instead of storing the Multiton instances directly, which can lead to creating unused instances, it stores the computation that leads to the creation of the Multiton. That additional layer solves the problem of unused instances.

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();
    }
}
Community
  • 1
  • 1
assylias
  • 321,522
  • 82
  • 660
  • 783
  • Like this! Why the `while (true)`? – OldCurmudgeon Aug 09 '13 at 15:01
  • In the original implementation, the client can cancel the task, in which case it makes sense (if two threads call getInstance on the same key and one cancels the task you don't want to penalise the other). In my example, it is redundant because it can't happen. I'll clean that. – assylias Aug 09 '13 at 15:07
  • Does `FutureTask` work predictably if `get` is called before `run`? – OldCurmudgeon Aug 09 '13 at 15:11
  • 1
    @OldCurmudgeon Yes it waits until run is called. – assylias Aug 09 '13 at 15:15
  • Is it possible with this memoizer approach to add parameters to the Multiton constructor? E. g. nameOfCreator, genderOfCreator. I can't think of any way since `Callable.call()` has a empty parameter list. – mike Aug 15 '13 at 13:00
  • 1
    @mike assuming the parameters are passed to `getInstance`, you can create the callable on the fly to return a `new Multiton(params)` instead of the standard `creator` one. I would also think that if the same key is called with different parameters, it could lead to some confusion... – assylias Aug 15 '13 at 13:27
  • I was just thinking to use the memoizer pattern in an abstract factory within another context because I liked it so much. Anyway, thx for the hint! – mike Aug 15 '13 at 13:37
  • 2
    What is the behavior of FutureTask if two threads call run()? I'm assuming the task is only run once, but the javadocs aren't 100% clear on that point. EDIT: I realize now that won't happen here anyway because run() is only called by the thread that successfully put() the FutureTask. – Bryan Rink Feb 21 '14 at 17:14
  • @assylias In the Singleton Pattern, the Obj reference is defined as `volatile`. How to make the Objects `volatile` here? – Pankaj Singhal Aug 10 '23 at 18:53
  • There is no need to make anything volatile, the thread safety is provided by the ConcurrentHashMap (which does use several volatile variables internally). – assylias Aug 12 '23 at 08:47
6

This will provide you a threadsafe storage mechanism for your Multitons. The only downside is that it is possible to create a Multiton that will not be used in the putIfAbsent() call. The possibility is small but it does exist. Of course on the remote chance it does happen, it still causes no harm.

On the plus side, there is no preallocation or initialization required and no predefined size restrictions.

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;
}
Yogesh Umesh Vaity
  • 41,009
  • 21
  • 145
  • 105
Robin
  • 24,062
  • 5
  • 49
  • 58
  • `putIfAbsent` returns `null` if there was no mapping for the key (see the [doc](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ConcurrentHashMap.html)). Thus, `result` will still be null the first time `getInstance` is called. – sp00m Jul 30 '13 at 14:48
  • @Robin In the Singleton Pattern, the Obj reference is defined as `volatile`. How to make the Objects `volatile` here? – Pankaj Singhal Aug 10 '23 at 18:52
4

You could use an array of locks, to at least be able to get different instances concurrently:

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];
    }
}
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Just beware that it's *much* harder to write the non-blocking version for this, because of volatile array semantics. – Voo Jun 20 '12 at 19:59
  • This will allow you to get different instances concurrently, but is there any workaround to the unnecessary synchronization around the individual instances? It seems like we are back to the double-checked locking dilemma at this point. – donnyton Jun 20 '12 at 21:03
  • 2
    The simplest workaround is to eagerly initialize all the instances. This is the simplest solution, and is the best one in many cases. If lazyness is an absolute requirement, first make sure that the simple synchronization constitutes a performance problem before trying to optimize it. Synchronization is fast, or at least much much faster than IO, database access, interprocess calls, O(n^2) algorithms, etc. Don't preoptimize. It's the root of all evil. – JB Nizet Jun 20 '12 at 21:07
  • @JBNizet In the Singleton Pattern, the Obj reference is defined as `volatile`. How to make the Objects `volatile` here? – Pankaj Singhal Aug 10 '23 at 18:50
3

With the advent of Java 8 and some improvements in ConcurrentMap and lambdas it is now possible to implement a Multiton (and probably even a Singleton) in a much tidier fashion:

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

I suspect - although it would make me feel uncomfortable - that getInstance could be further minimised to:

// 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());
}
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • Why does it make you feel uncomfortable? – assylias Mar 13 '14 at 14:42
  • @assylias - Because I am yet to get my head around the concept of passing parameters to methods that only potentially get executed. That `new Multiton()` stares at me and habit says *an object is being created*. I know it is irrational - I just have to deal with it. – OldCurmudgeon Mar 13 '14 at 14:56
  • @OldCurmudgeon In the Singleton Pattern, the Obj reference is defined as `volatile`. How to make the Objects `volatile` here? – Pankaj Singhal Aug 10 '23 at 18:52
2

You're looking for an 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;
  }
}
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • The problem with this solution is that it can end up calling the constructor of Multiton multiple times for the same key. If the else block is executed then newIt is discarded because another thread made the object as well. – Bryan Rink Feb 21 '14 at 17:00
  • 1
    @BryanRink - You are absolutely correct. There is a better one here [Is this a true Multiton and is it thread safe](http://codereview.stackexchange.com/q/39965/11913) and a Java 8 one [here](http://stackoverflow.com/a/20331869/823393) – OldCurmudgeon Feb 21 '14 at 17:26