2007/10/25

Singleton vs. Multiton & Synchronization

1. The classic java singleton synchronization problem

public class Singleton{
private static Singleton instance;

/** Prevents instantiating by other classes. */
private Singleton(){}

public static synchronized Singleton getInstance()
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
The synchronized keyword on the getInstance() method is the evil. In a multi-thread environment, every thread will be blocked by each other when trying to grab the singleton instance. This is the classic java singleton synchronization problem.

2. The traditional solution using a static variable

public class Singleton {
private final static Singleton instance = new Singleton();

/** Prevents instantiating by other classes. */
private Singleton() {}

public static Singleton getInstance() {
return instance;
}
}
The singleton instance is initialized during the class loading time. No synchronization is necessary on the getInstance level

3. The Classic synchronized Multiton

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

private Multiton(){}

public static Multiton getInstance(Object key){
synchronized (instances) {
// Our "per key" singleton
Multiton instance;
if ((instance = instances.get(key)) == null) {
// Lazily create instance and add it to the map
instance = new Multiton();
instances.put(key, instance);
}
return instance;
}
}
}
The Multiton pattern begins with the concept of the Singleton pattern and expands it into an object pool of keys to objects. Instead of having a single instance per runtime, the Multiton pattern ensures a single instance per key. It simplifies retrieval of shared objects in an application.

As we noticed from the above scriptlet, the access to the getInstance() method is synchronized to ensure the uniqueness of instance per key. This, again, is a performance bottleneck in multi-thread environment. The traditional 'static' solution for Singleton pattern can't be used here since the keys are passed into the Multiton on-the-fly. We have no way to predict how many keys will be passed into the Multiton during the runtime.

4. The double-checked locking solution

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

public static Multiton getInstance(Object key) {
// Our "per key" singleton
Multiton instance;
if ((instance = instances.get(key)) == null){
synchronized(instances){
if ((instance = instances.get(key)) == null){
instance = new Multiton();
instances.put(key, instance);
}
}
}
return instance;
}
}
Yes the double-checked locking does not work for Singleton (http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html), but it does work for our Multiton pattern. Reason? We have our intermediate HashMap object sits in between. Actually we can use the HashMap to solve the Singleton double-checked locking problem as well, if we don't already have the more elegant solution using a static variable.

5. An alternative solution: Using the Java 5 ConcurrentMap

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

public static Multiton getInstance(Object key) {
// Our "per key" singleton
if (instances.get(key) == null){
// Lazily create instance and try to add it to the map
Multiton instance = new Multiton();
instances.putIfAbsent(key, instance);
}
return instances.get(key);
}
}
The Java 5 ConcurrentMap.putIfAbsent(), being an atomic operation, returns the previous value associated with the key instead of the new suggested value. This ensures the uniqueness of the Multiton instance inside the ConcurrentMap.

In this solution, before a key object has been associated with a Multiton object, if two or more threads are trying to retrieve the instance with the same key object, it's possible that multiple Multiton instance will be created inside the if() block. However, the ConcurrentMap ensures that a same Multiton instance is to be returned to the caller. Extra instances are eligible for Garbage Collection as soon as the method returns. You may not want to use this solution if the creation of the Multiton instance is an expensive operation, as in certain situation.

6. Afterwords

Synchronization and double-checked locking is such an interesting classic java topic, I'm so much wanting to participating in any sort of discussion, it'll always be interesting :)

5 comments:

Java Blues said...

Handy tips.

Anonymous said...

I believe your solution using the HashMap (number 4) is not thread-safe. This is because one thread could be performing a 'destructive' operation on the HashMap at the same time as another thread is performing (any) operation on the HashMap. This is something that HashMap does not support.

To apply this general rule to your example - the destructive operation is the put on the last line of the synchronized block. The other operation is the get near the start of the method. At runtime, the first thread could reach the 'put' line, and invoke that method on the map. Before the 'put' returns, a second thread could invoke the 'get' method. This type of concurrent access is not supported by HashMap.

To help with reasoning why it is not supported, consider the case where the 'put' causes the HashMap to perform a 'rehash' (increasing number of buckets and shuffling entries around). In this case it is not surprising to know that the behaviour of a concurrent call to get(Object) is undefined. Although of course it's not actually necessary to know the implementation details of HashMap - the javadoc explains that this kind of access must be synchronized externally.

By swapping the HashMap for a Java 5 ConcurrentHashMap, the method is now thread-safe, and provides a useful alternative to solution 5 for cases where the construction of your Multiton object is expensive.

J2EE Blogger said...

Hah! You're right. That's a good point. Even though I made sure that there will ever be no more than 1 thread modifying the structure, it still must be synchronized.

An alternative to use ConcurrentHashMap is to only do the synchronization when we know somebody is going to modify the map (put operations are considered much less frequent than get operations), but then we might again run into memory barrier problems.

Anonymous said...

If you know for sure that no concurrent thread is making destructive calls on the HashMap, then you are okay to not synchronize access for non-destructive calls.

But if it *is* possible that a concurrent thread is making destructive calls then unfortunately the non-destructive calls have to use the same synchronization mechanism.

For a lazily-loaded cache, I would always use a ConcurrentHashMap. I understand that its pretty performant - much better than using a synchronized block or Collections.synchronizedMap() style maps. (I think I verifed its performance for myself once with a quick test).

Javin @ Tibco RV Tutorial said...

Hi,
Thanks for this Nice article just to add while discussing about HashMap its worth mentioning following questions which frequently asked in Java interviews now days like How HashMap works in Java or How get() method of HashMap works in JAVA very often. on concept point of view these questions are great and expose the candidate if doesn't know deep details.

Javin
Difference between FIX4.2 vs FIX4.4

Well well... why another J2EE blog? I benefited from other people's technical blogs, and guess what, it's a good idea to contribute some of my works too. Hope it's helpful and useful, to all of your folks.