Singleton Design Pattern


Overview

In this article we will discuss the following points:
Table of Contents
1. Intent
2. Explanation
3. Structure
4. Participants
5. Implementation
5.1 Thread-Safe Singleton
5.2 Double check locking
5.3 Eager Initialization 
5.4 Enum Singleton
6. Applicability
6.1 Typical Use Case
7. Real world examples
8. Consequences

1. Intent

Ensure a class only has one instance, and provide a global point of access to it.

2. Explanation

Real world example

There can only be one ivory tower where the wizards study their magic. The same enchanted ivory tower is always used by the wizards. The ivory tower here is a singleton.

In plain words

Ensures that only one object of a particular class is ever created.

Wikipedia says

In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.

3. Structure


4. Participants

Singleton
  • Defines an Instance operation that lets clients access its unique instance.
The instance is a class operation
  •  May be responsible for creating its own unique instance.

Tips to create Singleton design pattern

To create the singleton class, we need to have a static data member of the class, private constructor, and static factory method.
  • Static member: It gets memory only once because of static, it contains the instance of the Singleton class.
  • Private constructor: It will prevent to instantiate the Singleton class from outside the class.
  • Static factory method: This provides the global point of access to the Singleton object and returns the instance to the caller.

5. Implementation

5.1 Thread-Safe Singleton 

Lazy initialization method to implement Singleton pattern creates an instance in the global access method. Here is the sample code for creating Singleton class with this.
The instance is lazily initialized and thus needs a synchronization mechanism.
package com.ramesh.singleton;
public final class ThreadSafeLazyLoadedIvoryTower {

  private static ThreadSafeLazyLoadedIvoryTower instance;

  private ThreadSafeLazyLoadedIvoryTower() {
  // to prevent instantiating by Reflection call
    if (instance != null) {
      throw new IllegalStateException("Already initialized.");
    }
  }

  /**
   * The instance gets created only when it is called for first time. Lazy-loading
   */
  public static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() {

    if (instance == null) {
      instance = new ThreadSafeLazyLoadedIvoryTower();
    }

    return instance;
  }
}
Above implementation works fine and provides thread-safety but it reduces the performance because of the cost associated with the synchronized method, although we need it only for the first few threads who might create separate instances. To avoid this extra overhead every time, the double-checked locking principle is used. In this approach, the synchronized block is used inside if condition with an additional check to ensure that only one instance of a singleton class is created. 

5.2 Double check locking

public final class ThreadSafeDoubleCheckLocking {

  private static volatile ThreadSafeDoubleCheckLocking instance;

  /**
   * private constructor to prevent client from instantiating.
   */
  private ThreadSafeDoubleCheckLocking() {
    // to prevent instantiating by Reflection call
    if (instance != null) {
      throw new IllegalStateException("Already initialized.");
    }
  }

  /**
   * Public accessor.
   *
   * @return an instance of the class.
   */
  public static ThreadSafeDoubleCheckLocking getInstance() {
    // local variable increases performance by 25 percent
    // Joshua Bloch "Effective Java, Second Edition", p. 283-284
    
    ThreadSafeDoubleCheckLocking result = instance;
    // Check if singleton instance is initialized. If it is initialized then we can return the instance.
    if (result == null) {
      // It is not initialized but we cannot be sure because some other thread might have initialized it
      // in the meanwhile. So to make sure we need to lock on an object to get mutual exclusion.
      synchronized (ThreadSafeDoubleCheckLocking.class) {
        // Again assign the instance to local variable to check if it was initialized by some other thread
        // while current thread was blocked to enter the locked zone. If it was initialized then we can 
        // return the previously created instance just like the previous null check.
        result = instance;
        if (result == null) {
          // The instance is still not initialized so we can safely (no other thread can enter this zone)
          // create an instance and make it our singleton instance.
          instance = result = new ThreadSafeDoubleCheckLocking();
        }
      }
    }
    return result;
  }
}

5.3 Eager Initialization 



In eager initialization, the instance of Singleton Class is created at the time of class loading, this is the easiest method to create a singleton class but it has a drawback that instance is created even though client application might not be using it.

Eagerly initialized static instance guarantees thread safety.
package com.ramesh.singleton;
public final class IvoryTower {

  /**
   * Private constructor so nobody can instantiate the class.
   */
  private IvoryTower() {}

  /**
   * Static to class instance of the class.
   */
  private static final IvoryTower INSTANCE = new IvoryTower();

  /**
   * To be called by user to obtain instance of the class.
   *
   * @return instance of the singleton.
   */
  public static IvoryTower getInstance() {
    return INSTANCE;
  }
}
The Initialize-on-demand-holder idiom is a secure way of creating a lazy initialized singleton object in Java.
package com.ramesh.singleton;

public final class InitializingOnDemandHolderIdiom {

  /**
   * Private constructor.
   */
  private InitializingOnDemandHolderIdiom() {}

  /**
   * @return Singleton instance
   */
  public static InitializingOnDemandHolderIdiom getInstance() {
    return HelperHolder.INSTANCE;
  }

  /**
   * Provides the lazy-loaded Singleton instance.
   */
  private static class HelperHolder {
    private static final InitializingOnDemandHolderIdiom INSTANCE =
        new InitializingOnDemandHolderIdiom();
  }
}

5.4 Enum Singleton 

To overcome this situation with Reflection, Joshua Bloch suggests the use of Enum to implement a Singleton design pattern as Java ensures that any enum value is instantiated only once in a Java program. Since Java Enum values are globally accessible, so is the singleton. The drawback is that the enum type is somewhat inflexible; for example, it does not allow lazy initialization.
package com.ramesh.singleton;

/**
 * Enum based singleton implementation. Effective Java 2nd Edition (Joshua Bloch) p. 18
 */
public enum EnumIvoryTower {

  INSTANCE;

  @Override
  public String toString() {
    return getDeclaringClass().getCanonicalName() + "@" + hashCode();
  }
}

5.5 Test singleton design pattern

public class App {

  private static final Logger LOGGER = LoggerFactory.getLogger(App.class);

  /**
   * Program entry point.
   *
   * @param args command line args
   */
  public static void main(String[] args) {

    // eagerly initialized singleton
    IvoryTower ivoryTower1 = IvoryTower.getInstance();
    IvoryTower ivoryTower2 = IvoryTower.getInstance();
    LOGGER.info("ivoryTower1={}", ivoryTower1);
    LOGGER.info("ivoryTower2={}", ivoryTower2);

    // lazily initialized singleton
    ThreadSafeLazyLoadedIvoryTower threadSafeIvoryTower1 =
        ThreadSafeLazyLoadedIvoryTower.getInstance();
    ThreadSafeLazyLoadedIvoryTower threadSafeIvoryTower2 =
        ThreadSafeLazyLoadedIvoryTower.getInstance();
    LOGGER.info("threadSafeIvoryTower1={}", threadSafeIvoryTower1);
    LOGGER.info("threadSafeIvoryTower2={}", threadSafeIvoryTower2);

    // enum singleton
    EnumIvoryTower enumIvoryTower1 = EnumIvoryTower.INSTANCE;
    EnumIvoryTower enumIvoryTower2 = EnumIvoryTower.INSTANCE;
    LOGGER.info("enumIvoryTower1={}", enumIvoryTower1);
    LOGGER.info("enumIvoryTower2={}", enumIvoryTower2);

    // double checked locking
    ThreadSafeDoubleCheckLocking dcl1 = ThreadSafeDoubleCheckLocking.getInstance();
    LOGGER.info(dcl1.toString());
    ThreadSafeDoubleCheckLocking dcl2 = ThreadSafeDoubleCheckLocking.getInstance();
    LOGGER.info(dcl2.toString());

    // initialize on demand holder idiom
    InitializingOnDemandHolderIdiom demandHolderIdiom =
        InitializingOnDemandHolderIdiom.getInstance();
    LOGGER.info(demandHolderIdiom.toString());
    InitializingOnDemandHolderIdiom demandHolderIdiom2 =
        InitializingOnDemandHolderIdiom.getInstance();
    LOGGER.info(demandHolderIdiom2.toString());
  }
}

6. Applicability

Use the Singleton pattern when
  • there must be exactly one instance of a class, and it must be accessible to clients from a well-known access point.
  • when the sole instance should be extensible by subclassing, and clients should be able to use an extended instance without modifying their code.

6.1 Typical Use Case

Singleton pattern is used for loggingdriver objectscaching and thread pool. Singleton design pattern is also used in other design patterns like Abstract Factory, Builder, Prototype, Facade etc. 

7. Real world examples

8. Consequences

Violates the Single Responsibility Principle (SRP) by controlling their own creation and lifecycle.
  • Encourages using a global shared instance which prevents an object and resources used by this object from being deallocated.
  • Creates tightly coupled code. The clients of the Singleton become difficult to test.
  • Makes it almost impossible to subclass a Singleton.

9. Credits

  • Design Patterns: Elements of Reusable Object-Oriented Software
  • Effective Java (2nd Edition)

The source code of Singleton pattern on my GitHub repository :

Comments

  1. You pose an interesting concepts given the current development environments.

    ReplyDelete
    Replies
    1. Thank you. I have also posted J2EE design patterns . You can read these blogs and implement in your current projects.

      Delete
  2. Really nice article and well explained. But this is for single threaded applications. So what about multiple thread application. I found a really great explanation and solution there.

    ReplyDelete

Post a Comment