Wednesday, December 8, 2010

Singleton

“Design patterns are recurring solutions to design problems.”
Java Design patterns are grouped under three categories:
                1. Creational Patterns
                2. Structural Patterns
                3. Behavioral Patterns
Singleton Pattern being the most commonly used creational design pattern. This design pattern proposes that at any time there can be only one instance of a singleton (object) created by the JVM.

                The class’s default constructor is made private, which prevents the direct instantiation of the object by others (Other Classes). A static modifier is applied to the instance method that returns the object as it without creating an object.
1)   Creating  the normal Singleton class
class Singleton
{
  private static Singleton instance;
  private Singleton()  {  }
  public static Singleton getInstance() 
  {
    if (instance == null)                                     //1
 instance = new Singleton();        //2
    return instance;                                            //3
  }
}
The design of this class ensures that only one Singleton object is ever created. The constructor is declared private and the getInstance() method creates only one object. This implementation is fine for a single-threaded program. However, when multiple threads are introduced, you must protect the getInstance() method through synchronization. If the getInstance() method is not protected, it is possible to return two different instances of the Singleton object.
2)   Creating the Thread-safe getInstance() method

public static synchronized Singleton getInstance()
{
  if (instance == null)                       //1
    instance = new Singleton();      //2
  return instance;                               //3
}
The code in step 2 works fine for multithreaded access to the getInstance() method. However, when you analyze it you realize that synchronization is required only for the first invocation of the method. Subsequent invocations do not require synchronization because the first invocation is the only invocation that executes the code at //2, which is the only line that requires synchronization. All other invocations determine that instance is non-null and return it. Multiple threads can safely execute concurrently on all invocations except the first. However, because the method is synchronized, you pay the cost of synchronization for every invocation of the method, even though it is only required on the first invocation.
In an effort to make this method more efficient, an idiom called double-checked locking was created. The idea is to avoid the costly synchronization for all invocations of the method except the first. The cost of synchronization differs from JVM to JVM. In the early days, the cost could be quite high. As more advanced JVMs have emerged, the cost of synchronization has decreased, but there is still a performance penalty for entering and leaving a synchronized method or block. Regardless of the advancements in JVM technology, programmers never want to waste processing time unnecessarily.
Because only line //2 in step 2 requires synchronization, we could just wrap it in a synchronized block, as shown in step 3:

3)   The getInstance() method
public static Singleton getInstance()
{
  if (instance == null)
  {
    synchronized(Singleton.class) {
      instance = new Singleton();
    }
  }
  return instance;
}
The code in step 3 exhibits the same problem as demonstrated with multiple threads and step 1. Two threads can get inside if statement concurrently when instance is null. Then, one thread enters the synchronized block to initialize instance, while the other is blocked. When the first thread exits the synchronized block, the waiting thread enters and creates another Singleton object. Note that when the second thread enters the synchronized block, it does not check to see if instance is non-null.

Double-checked locking

To fix the problem in step 3, we need a second check of instance. Thus, the name "double-checked locking."  Applying the double-checked locking idiom to step 3 results in step 4

4)   Double-checked locking example
public static Singleton getInstance()
{
  if (instance == null)
  {
    synchronized(Singleton.class) {  //1
      if (instance == null)          //2
        instance = new Singleton();  //3
    }
  }
  return instance;
}
The theory behind double-checked locking is perfect. Unfortunately, reality is entirely different. The problem with double-checked locking is that there is no guarantee it will work on single or multi-processor machines.

The issue of the failure of double-checked locking is not due to implementation bugs in JVMs but to the current Java platform memory model. The memory model allows what is known as "out-of-order writes" and is a prime reason why this idiom fails.

Out-of-order writes

To illustrate the problem, you need to re-examine line //3 from step 4 above. This line of code creates a Singleton object and initializes the variable instance to refer to this object. The problem with this line of code is that the variable instance can become non-null before the body of the Singleton constructor executes.
Given that the current double-checked locking code does not work, I've put together another version of the code, shown in step 5, to try to prevent the out-of-order write problem.

5)   Attempting to solve the out-of-order write problem
public static Singleton getInstance()
{
  if (instance == null)
  {
    synchronized(Singleton.class) {      //1
      Singleton inst = instance;         //2
      if (inst == null)
      {
        synchronized(Singleton.class) {  //3
          inst = new Singleton();        //4
        }
        instance = inst;                 //5
      }
    }
  }
  return instance;
}
Looking at the code in step 5 you should realize that things are getting a little ridiculous. Remember, double-checked locking was created as a way to avoid synchronizing the simple three-line getInstance() method. The code in step 5 has gotten out of hand. In addition, the code does not fix the problem
The code in step 5 doesn't work because of the current definition of the memory model. The Java Language Specification (JLS) demands that code within a synchronized block not be moved out of a synchronized block. However, it does not say that code not in a synchronized block cannot be moved into a synchronized block.
A JIT compiler would see an optimization opportunity here. This optimization would remove the code at //4 and the code at //5, combine it and generate the code shown in step 6:

6)   Optimized code from step 5
public static Singleton getInstance()
{
  if (instance == null)
  {
    synchronized(Singleton.class) {      //1
      Singleton inst = instance;         //2
      if (inst == null)
      {
        synchronized(Singleton.class) {  //3
          //inst = new Singleton();      //4
          instance = new Singleton();              
        }
        //instance = inst;               //5
      }
    }
  }
  return instance;
}
The bottom line is that double-checked locking, in whatever form, should not be used because you cannot guarantee that it will work on any JVM implementation. JSR-133 is addressing issues regarding the memory model; however, double-checked locking will not be supported by the new memory model. Therefore, you have two options:
  • Accept the synchronization of a getInstance() method as shown in step 2.
  • Forgot synchronization and use a static field.
  • Use a static inner class and create the static final instance inside the class.
Option 2 is shown in Step 7:
7)   Singleton implementation with static field

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

  private Singleton()
  {
    //...
  }
  public static Singleton getInstance()
  {
    return instance;
  }
}
Option 3 is shown in Step 8:
8)   Singleton implementation with static inner class

public class Singleton {
  // Private constructor prevents instantiation from other classes
  private Singleton() {}

  /**
   * SingletonHolder is loaded on the first execution of Singleton.getInstance()
   * or the first access to SingletonHolder.INSTANCE, not before.
   */
  private static class SingletonHolder {
    private static final Singleton INSTANCE = new Singleton();
  }

  public static Singleton getInstance() {
    return SingletonHolder.INSTANCE;
  }
}

No comments:

Post a Comment