2

Inspired by a comment to an given answer I tried to create a thread-safe implementation of the multiton pattern, which relies on unique keys and performs locks on them (I have the idea from JB Nizet's answer on this question).

Question

Is the implementation I provided viable?

I'm not interested in whether Multiton (or Singleton) are in general good patterns, it would result in a discussion. I just want a clean and working implementation.

Contras:

  • You have to know how many instances you want to create at compile time .

Pros

  • No lock on whole class, or whole map. Concurrent calls to getInstanceare possible.
  • Getting instances via key object, and not just unbounded int or String, so you can be sure to get an non-null instance after the method call.
  • Thread-safe (at least that's my impression).

public class Multiton
{
  private static final Map<Enum<?>, Multiton> instances = new HashMap<Enum<?>, Multiton>();

  private Multiton() {System.out.println("Created instance."); }

  /* Can be called concurrently, since it only synchronizes on id */
  public static <KEY extends Enum<?> & MultitionKey> Multiton getInstance(KEY id)
  {
    synchronized (id)
    {
      if (instances.get(id) == null)
        instances.put(id, new Multiton());
    }
    System.out.println("Retrieved instance.");
    return instances.get(id);
  }

  public interface MultitionKey { /* */ }

  public static void main(String[] args) throws InterruptedException
  {
    //getInstance(Keys.KEY_1);
    getInstance(OtherKeys.KEY_A);

    Runnable r = new Runnable() {
      @Override
      public void run() { getInstance(Keys.KEY_1); }
    };

    int size = 100;
    List<Thread> threads = new ArrayList<Thread>();
    for (int i = 0; i < size; i++)
      threads.add(new Thread(r));

    for (Thread t : threads)
      t.start();

    for (Thread t : threads)
      t.join();
  }

  enum Keys implements MultitionKey
  {
    KEY_1;

    /* define more keys */
  }

  enum OtherKeys implements MultitionKey
  {
    KEY_A;

    /* define more keys */
  }
}

I tried to prevent the resizing of the map and the misuse of the enums I sychronize on. It's more of a proof of concept, before I can get it over with! :)

public class Multiton
{
  private static final Map<MultitionKey, Multiton> instances = new HashMap<MultitionKey, Multiton>((int) (Key.values().length/0.75f) + 1);
  private static final Map<Key, MultitionKey> keyMap;

  static
  {
    Map<Key, MultitionKey> map = new HashMap<Key, MultitionKey>();
    map.put(Key.KEY_1, Keys.KEY_1);
    map.put(Key.KEY_2, OtherKeys.KEY_A);
    keyMap = Collections.unmodifiableMap(map);
  }

  public enum Key {
    KEY_1, KEY_2;
  }

  private Multiton() {System.out.println("Created instance."); }

  /* Can be called concurrently, since it only synchronizes on KEY */
  public static <KEY extends Enum<?> & MultitionKey> Multiton getInstance(Key id)
  {
    @SuppressWarnings ("unchecked")
    KEY key = (KEY) keyMap.get(id);
    synchronized (keyMap.get(id))
    {
      if (instances.get(key) == null)
        instances.put(key, new Multiton());
    }
    System.out.println("Retrieved instance.");
    return instances.get(key);
  }

  private interface MultitionKey { /* */ }

  private enum Keys implements MultitionKey
  {
    KEY_1;

    /* define more keys */
  }

  private enum OtherKeys implements MultitionKey
  {
    KEY_A;

    /* define more keys */
  }
}
Community
  • 1
  • 1
mike
  • 4,929
  • 4
  • 40
  • 80
  • This should be ported to [codereview.stackexhange.com](http://codereview.stackexchange.com) – Ibrahim Najjar Aug 09 '13 at 13:01
  • 1
    I think it can stay on SO... Anyhow, I changed the Code, more sets of enums are accepted now. – mike Aug 09 '13 at 13:08
  • 1
    FYI I have added an implementation on the other question: http://stackoverflow.com/a/18149547/829571 It is lock free and thread safe (unless I've missed something). – assylias Aug 09 '13 at 14:50

3 Answers3

3

It is absolutely not thread-safe. Here is a simple example of the many, many things that could go wrong.

Thread A is trying to put at key id1. Thread B is resizing the buckets table due to a put at id2. Because these have different synchronization monitors, they're off to the races in parallel.

Thread A                      Thread B
--------                      --------
b = key.hash % map.buckets.size   

                             copy map.buckets reference to local var
                             set map.buckets = new Bucket[newSize]
                             insert keys from old buckets into new buckets

insert into map.buckets[b]

In this example, let's say Thread A saw the map.buckets = new Bucket[newSize] modification. It's not guaranteed to (since there's no happens-before edge), but it may. In that case, it'll be inserting the (key, value) pair into the wrong bucket. Nobody will ever find it.

As a slight variant, if Thread A copied the map.buckets reference to a local var and did all its work on that, then it'd be inserting into the right bucket, but the wrong buckets table; it wouldn't be inserting into the new one that Thread B is about to install as the table for everyone to see. If the next operation on key 1 happens to see the new table (again, not guaranteed to but it may), then it won't see Thread A's actions because they were done on a long-forgotten buckets array.

yshavit
  • 42,327
  • 7
  • 87
  • 124
  • If the initial capacity of the map is set to `(int)(N/0.75F)+1` it can't break since the number of instances if known from the beginning. At least I think so. http://stackoverflow.com/questions/13329819/why-hashmap-initial-capacity-is-not-properly-handled-by-the-library – mike Aug 15 '13 at 18:20
  • 1
    That's just one example. You could also have a race condition in adding two elements to a bucket's list in the case of a collision. There also memory visibility considerations; you could have a condition where `!map.isEmpty()` but it has no elements. Etc etc etc. The point is, mutable data structures that aren't thread-safe are, well, not safe for multi-threaded access. – yshavit Aug 15 '13 at 18:40
  • I don't fully agree. It depends on how you use them. The main problem here is the `put` and the consequences you pointed out. But yes, it would be easier in this case to just use a `ConcurrentHashMap` and focus on other stuff. But I was curious! Thx! – mike Aug 15 '13 at 18:49
2

I'd say not viable.

Synchronizing on the id parameter is fraught with dangers - what if they use this enum for another synchronization mechanism? And of course HashMap is not concurrent as the comments have pointed out.

To demonstrate - try this:

Runnable r = new Runnable() {
  @Override
  public void run() { 
    // Added to demonstrate the problem.
    synchronized(Keys.KEY_1) {
      getInstance(Keys.KEY_1);
    } 
  }
};

Here's an implementation that uses atomics instead of synchronization and therefore should be more efficient. It is much more complicated than yours but handling all of the edge cases in a Miltiton IS complicated.

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

  // Ready for use - set to false while initialising.
  private final AtomicBoolean ready = new AtomicBoolean();
  // Everyone who is waiting for me to initialise.
  private final Queue<Thread> waiters = new ConcurrentLinkedQueue<>();
  // For logging (and a bit of linguistic fun).
  private final int forInstance;

  // We need a simple constructor.
  private Multiton(int forInstance) {
    this.forInstance = forInstance;
    log(forInstance, "New");
  }

  // The expensive initialiser.
  public void init() throws InterruptedException {
    log(forInstance, "Init");
    // ... presumably heavy stuff.
    Thread.sleep(1000);

    // We are now ready.
    ready();
  }

  private void ready() {
    log(forInstance, "Ready");
    // I am now ready.
    ready.getAndSet(true);
    // Unpark everyone waiting for me.
    for (Thread t : waiters) {
      LockSupport.unpark(t);
    }
  }

  // Get the instance for that one.
  public static Multiton getInstance(int which) throws InterruptedException {
    // One there already?
    Multiton it = instances.get(which);
    if (it == null) {
      // Lazy make.
      Multiton newIt = new Multiton(which);
      // Successful put?
      if (instances.compareAndSet(which, null, newIt)) {
        // Yes!
        it = newIt;
        // Initialise it.
        it.init();
      } else {
        // One appeared as if by magic (another thread got there first).
        it = instances.get(which);
        // Wait for it to finish initialisation.
        // Put me in its queue of waiters.
        it.waiters.add(Thread.currentThread());
        log(which, "Parking");
        while (!it.ready.get()) {
          // Park me.
          LockSupport.park();
        }
        // I'm not waiting any more.
        it.waiters.remove(Thread.currentThread());
        log(which, "Unparked");
      }
    }

    return it;
  }

  // Some simple logging.
  static void log(int which, String s) {
    log(new Date(), "Thread " + Thread.currentThread().getId() + " for Multiton " + which + " " + s);
  }
  static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
  // synchronized so I don't need to make the DateFormat ThreadLocal.

  static synchronized void log(Date d, String s) {
    System.out.println(dateFormat.format(d) + " " + s);
  }

  // The tester class.
  static class MultitonTester implements Runnable {
    int which;

    private MultitonTester(int which) {
      this.which = which;
    }

    @Override
    public void run() {
      try {
        Multiton.log(which, "Waiting");
        Multiton m = Multiton.getInstance(which);
        Multiton.log(which, "Got");
      } catch (InterruptedException ex) {
        Multiton.log(which, "Interrupted");
      }
    }
  }

  public static void main(String[] args) throws InterruptedException {
    int testers = 50;
    int multitons = 50;
    // Do a number of them. Makes n testers for each Multiton.
    for (int i = 1; i < testers * multitons; i++) {
      // Which one to create.
      int which = i / testers;
      //System.out.println("Requesting Multiton " + i);
      new Thread(new MultitonTester(which+1)).start();
    }

  }
}
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • 2
    @assylias - That's very clever! I like that a lot. – OldCurmudgeon Aug 09 '13 at 14:59
  • Yes, you're right. But only if another thread uses the enums for **different** purposes. Adding the synchronzied block changes nothing, since the thread synchronizing is the monitor and thus can go on with `getInstance()`. At least I think so. – mike Aug 09 '13 at 15:17
  • 1
    @mike - In this case you are right but if the other `synchronized` was in a different thread it will block and break. This is a common mistake with threads. You should only block on a private object or you risk really weird bugs that are well-night impossible to trace. – OldCurmudgeon Aug 09 '13 at 21:22
  • I thought about it. There is a little trick to solve the issue with others synchronzing on the enum keys. We have a public enum key that is internally used to retrieve the private enum key (which is stored in an unmodifiable map created in a static block). We then synchronize on the private key. – mike Aug 15 '13 at 18:23
0

I'm not a Java programmer, but: HashMap is not safe for concurrent access. Might I recommend ConcurrentHashMap.

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

  public static <TYPE extends Object, KEY extends Enum<Keys> & MultitionKey<TYPE>> Multiton getInstance(KEY id)
  {
    Multiton result;
    synchronized (id)
    {
      result = instances.get(id);
      if(result == null)
      {
        result = new Multiton();
        instances.put(id, result);
      }
    }
    System.out.println("Retrieved instance.");
    return result;
  }
Dark Falcon
  • 43,592
  • 5
  • 83
  • 98
  • I know that already, but your solution has the drawback, that while accessing the concurrent hashmap it locks, and you can't get other multiton instances out of it. Since I lock on the key you can call `getInstance` concurrently. – mike Aug 09 '13 at 13:12
  • No it does not lock in a way that prevents accessing other instances. I am locking on the ID, and: "Retrieval operations (including get) generally do not block, so may overlap with update operations (including put and remove)." So accesses to the instance with the same ID will block, but different IDs will not. – Dark Falcon Aug 09 '13 at 13:13
  • Hmm, okay. But since `put` can't be called simultaneously with the same key. Wouldn't a 'normal' map be sufficient. Or could you point out where problems in my implementation could occur? – mike Aug 09 '13 at 13:18
  • 3
    The problem is that one thread calling `put` with a different key can still corrupt a `Map` if another thread is putting a different key. This is because the implementation of `HashMap.put` is not necessarily guaranteed to be atomic. `ConcurrentHashMap` solves this by making `put` atomic. – Dark Falcon Aug 09 '13 at 13:21
  • Okay, the problem is, when a `put` operation triggers a rebuild of the table. http://stackoverflow.com/a/2688817/1809463 – mike Aug 09 '13 at 13:35
  • 1
    Not only when it triggers a rebuild, if by that you mean resizing the buckets table. Even a non-resizing `put` could have race conditions with other `put`s to the same bucket, or memory visibility races if the operations happen on different cores. – yshavit Aug 09 '13 at 14:43