It’s a Creational Design Pattern which talks about maintaining a single instance of a class throughout the application lifetime i.e., from start till the end of runtime process.

Purpose: When we want to have a globally accessible object that is created only once within the application. Examples: Application Logging, Application Configuration Settings, in memory cache.

This post will have code Snippets in Java.

There are multiple ways to implement Singleton Pattern, I will try to depict some popular variants.

  • Eagerly initialized Singleton

    public class EagerSingleton {
      private static final EagerSingleton INSTANCE = new EagerSingleton();
    
      private EagerSingleton(){}
    
      public static EagerSingleton getInstance() {
        return INSTANCE;
      }
    }
    
  • Lazily initialized Singleton

    public class LazySingleton {
      private static LazySingleton instance;
        
      private LazySingleton() {}
    
      public static LazySingleton getInstance() {
        if(instance == null) {
          instance = new LazySingleton();
        }
        return instance;
      }
    }
    
    • Problem: In concurrent environments it will fail miserably by creating more than one object and the code is not thread-safe.
    • Solution: Locking the critical section is vital in multithreaded environments, and in programming languages like Java there is more than one way to do this locking, but will focus on how it can be achieved through synchronized keyword.
  • Lazily initialized Singleton - Improved to work in multithreaded environments

    public class LazySingleton {
      /*- 
        volatile is required here to save the instance in main memory 
        which ensures that threads operating upon this object gets the 
        latest value. Without volatile each thread maintains local copy 
        of static variable, and each thread is unaware if the instance 
        variable is already initialized
      */
      private static volatile LazySingleton instance;
    
      private LazySingleton() {}
    
      public synchronized static LazySingleton getInstance() {
        if(instance == null) {
          instance = new LazySingleton();
        }
        return instance;
      }
    }
    
    • Problem: Applying synchronized at method level does lock above and beyond the critical section resulting in slightly less performant code.
    • Solution: Applying synchronized at block level instead of method level would bring some improvements to performance, by locking only the critical section.
  • Lazily initialized Singleton - Double-Checked Locking - Performance improvement

    public class LazySingleton {
      /*- 
        volatile is required here to save the instance in main memory 
        which ensures that threads operating upon this object gets the 
        latest value. Without volatile each thread maintains local copy 
        of static variable, and each thread is unaware if the instance 
        variable is already initialized
      */
      private static volatile LazySingleton instance;
        
      private LazySingleton() {}
    
      public static LazySingleton getInstance() {
        if(instance == null) {
          synchronized(LazySingleton.class) { // Class Level Lock
            if(instance == null) { // Double-Check
              instance = new LazySingleton();
            }
          }
        }
        return instance;
      }
    }
    
  • Alternatively, there is neat approach to implement Singleton without using synchronized and yet achieve thread-safety. It’s known as Initialization on demand

    public class LazySingleton {
      private LazySingleton() {}
    
      private static class LazySingletonHolder {
        private static final LazySingleton INSTANCE = new LazySingleton();
      }
    
      public static LazySingleton getInstance() {
        return LazySingletonHolder.INSTANCE;
      }
    }
    
  • Common Problems in all the above Singleton implementations:
    • Pattern can be broken by creating multiple objects through Reflection
    • And, there are other petty complaints
      • It can be cloneable. Yes it is possible only when we implement Cloneable interface and override java.lang.Object’s clone() method. But why would anyone want to implement Cloneable interface in a Singleton?
      • It has serialization issues. Yes it has this problem exposed when we implement Serializable interface, and it creates a new object during deserialization process.
        • Below is the excerpt from my edited StackOverflow Answer

          During deserialization process we call readObject() which is an existing method in ObjectInputStream class. At the time of deserialization readObject() method internally checks whether the object that is being deserialized has readResolve() method implemented. If readResolve() method exists then it will be invoked. A sample readResolve() implementation would look like this.

            protected Object readResolve() {
              return INSTANCE;
            }
          

          So, the intent of writing readResolve() method is to ensure that the same object that lives in JVM is returned instead of creating new object during deserialization.

      • But, the real question is why do you want to serialize a Singleton?
  • Finally, the recommended approach for implementing Singleton in Java is using enum

    public enum UserInfo {
      SRK("SaiRaghava K", 99);
    
      private String name;
      private int age;
    
      UserInfo(String name, int age) {
        this.name = name;
        this.age = age;
      }
    
      public int getAge() {
        return age;
      }
    
      public String getName() {
        return name;
      }
    }
    
    • An enum implicitly implements Serializable, and it ensures that same object is returned during deserialization as well. Unlike a Singleton that’s implemented using class construct, in enum based singletons we don’t have to add readResolve() method which guarantees that only a single instance is maintained.
    • Singleton pattern implemented using class can be broken using reflection, however this is restricted for enum based Singleton implementation. If any such attempt is made, it might result in exception Stacktrace as shown below.

        Exception in thread "main" java.lang.IllegalArgumentException: 
        Cannot reflectively create enum objects
      
  • In sum, it’s recommended to implement Singleton pattern using enum in Java, and it’s crucial to keep the Singleton state immutable.

References:

  1. Singleton Pattern
  2. readResolve()