Saturday, October 1, 2011

Double Checking Locks for Singletons

I'm not going to argue the benefits of factories and singletons in this article.  The jury is in on these topics and yes, it makes sense to spend time making boiler plate code in an effort to decouple implementations from your code.

The issue with lazy loading singletons is that there are times when singletons don't behave as you expect and in my experience, this usually indicates that you may not have a singleton after all.  Consider the following code:

public class AFactory
{
    private static final Map<TYPE,ISomething> cache = 
       new HashMap<TYPE,ISomething>();
    public static ISomething getInstance(TYPE type) {
         ISomething ret = cache.get(type);
         if ( ret == null ) {
              ret = new SimpleSomething();
              cache.put(TYPE,ret);
         }
         return ret;
    }
}

The benefits of this pattern are simple.  I can now code to the interface instead of the implementation, so if I want to hijack this factory for testing, I can simply add code like:

public static void setInstance(TYPE type, ISomething imp) {
    cache.put(type,imp);
}

and then I can put a mock in the cache for testing.  Other benefits may be to open a single connection and reuse it over and over again, or whatever.

The problem with this type of pattern is that I can get into a spot where two threads might go looking for an instance of ISomething in the cache at the same time...if no instance is there, then two separate instances can get created and the last one put in the map will be persisted.  This can cause unexpected results in some cases...especially if your SimpleSomething instance has some initialization that implements a listener, or registers with a management server or some other magic.

We could avoid this simply by synchronizing the call to getInstance, but this is needless in most cases and will cause the system to slow down as threads have to wait in line to get an instance from your cache.

The solution is somewhere in the middle.  I've heard the idea referred to in a couple of ways, but I like to call it a double checking lock.  Have a look at the slight modification to the factory method:


public static ISomething getInstance(TYPE type) {
     ISomething ret = cache.get(type);
     if ( ret == null ) {
          synchronized(cache) {
              ret = cache.get(type);
              if ( ret == null ) {
                  ret = new SimpleSomething();
                  cache.put(TYPE,ret);
              }
         }
     }
     return ret;
}

By adding a synchronized block on a cache miss, and then checking again, this forces threads accessing the factory before the cache is initialized with the implementation you are looking for to synchronize and give the application time to populate the cache.  Once it is populated, no synchronization is required since subsequent calls will hit the cache for the implementation.

No comments: