Blog Name

Unlocking the Secrets of Java Singleton Design Pattern: Best Practices and Implementation Techniques

Monday, Jan 20, 2021
2 min read

What is the purpose of Singleton Design pattern?

A singleton is a design pattern commonly used in object-oriented programming. It restricts the creation of multiple instances of a class and guarantees that only one instance exists throughout the program. This pattern is useful when you want to control access to a shared resource or when there should be a single point of coordination or configuration.

The term "Singleton" in software development, derived from its mathematical counterpart, signifies the desire for having only one instance per context. In the Java programming language, this translates to ensuring one instance per Java Virtual Machine (JVM).

To implement a singleton, the class typically provides a static method or property that returns the single instance. The first time this method is called, it creates an instance of the class and stores it. Subsequent calls to the method return the same instance, ensuring consistent access to the object.

Singletons are often used in scenarios where multiple objects accessing the same data or resources could lead to conflicts or inefficient resource usage. By enforcing a single instance, singletons can simplify coordination and ensure that all components use a consistent state.

However, it's worth noting that singletons can introduce challenges, such as tight coupling and difficulties in testing. Careful consideration should be given to the usage and necessity of a singleton in a given design.

The Java classes, java.awt.Desktop and java.lang.runtime also use a singleton pattern.

The Java classes, java.awt.Desktop and java.lang.runtime also use a singleton pattern..


Principles for Implementing the Singleton Design Pattern

The implementation of the Singleton design pattern can be guided by several principles to ensure its effectiveness and proper usage. These principles include:

  • Single Instance: The Singleton pattern guarantees that only one instance of the class exists throughout the application. It is important to enforce this principle and prevent multiple instances from being created.

  • Global Access: The Singleton instance should be globally accessible to all parts of the application. This allows consistent access to the singleton object and ensures that all components can interact with it when needed.

  • Private Constructor: The class implementing the Singleton pattern should have a private constructor to prevent external instantiation. This ensures that the class cannot be directly instantiated by other parts of the application.

  • Lazy Initialization (Optional): The Singleton instance can be lazily initialized, meaning it is created when it is first requested rather than at application startup. This approach can improve performance by deferring the creation of the instance until it is actually needed.

  • Lazy Initialization (Optional): The Singleton instance can be lazily initialized, meaning it is created when it is first requested rather than at application startup. This approach can improve performance by deferring the creation of the instance until it is actually needed.

  • Thread Safety: If the Singleton is accessed by multiple threads concurrently, it is crucial to ensure thread safety. Synchronization mechanisms, such as locks or atomic operations, should be implemented to prevent race conditions and maintain consistency.

  • Serialization Compatibility (Optional): If the Singleton object needs to be serialized or deserialized, special consideration should be given to maintain its unique instance status during the serialization process.

  • Avoid Global State Abuse: While the Singleton pattern provides a convenient global access point, it is essential to use it judiciously. Excessive reliance on global state can lead to code that is hard to maintain and test. Consider using dependency injection or other architectural patterns to manage dependencies effectively.

By adhering to these principles, developers can effectively implement the Singleton pattern in their applications, ensuring the proper utilization of a single instance while maintaining code clarity and maintainability.

To create a Singleton class

The easiest method is to make the class constructor private. This prevents other parts of the code from directly creating instances of the class. There are two ways to initialize a Singleton class: Eager Initialization, Lazy Initialization Both approaches have their own advantages and considerations. The choice between them depends on the specific requirements and circumstances of the program.

Singleton with eager initialization

In this approach, the Singleton instance is created immediately when the class is loaded or at the start of the program. This ensures that the instance is available whenever needed.

The disadvantages of Eager Initialization for Singleton are that it creates the instance even when not immediately needed, leading to potential memory and performance inefficiencies, and it lacks support for conditional or dynamic instance creation, limiting flexibility in certain scenarios.

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

  private Singleton() {
      // Private constructor to prevent external instantiation
  }

  public static Singleton getInstance() {
      return instance;
  }
}

In this code, a Singleton class is defined. The instance variable is declared as private static final, ensuring that only one instance of the Singleton class exists throughout the program. The constructor is marked as private, preventing external instantiation of the class. The getInstance() method is a static method that returns the instance of the Singleton.

In Java, if two objects are the same, their hash keys should be equal. Let's test this with the implemented Singleton.

public class SingletonTester {
  public static void main(String[] args) {
      //Instance 1
      Singleton instance1 = Singleton.getInstance();

      //Instance 2
      Singleton instance2 = Singleton.getInstance();
      //now lets check the hash key.
      System.out.println("Instance 1 hash:" + instance1.hashCode());
      System.out.println("Instance 2 hash:" + instance2.hashCode());
  }
}

//OUTPUT
Instance 1 hash:918221580
Instance 2 hash:918221580
                    

You can see that both the instances are having the same hash code. So, that means above code will make the perfect Singleton.

Singleton with Lazy initialization

In this approach, the Singleton instance is created lazily, meaning it is created when it is first requested.

public class Singleton {
  private static Singleton instance;

  private Singleton() {
      // Private constructor to prevent external instantiation
  }

  public static Singleton getInstance() {
      if (instance == null) {
          instance = new Singleton();
      }
      return instance;
  }
}

Make Singleton reflection proof

The Singleton class shown above can be susceptible to multiple instances being created using Java Reflection. Reflection allows for examining or modifying the behavior of a class at runtime. By changing the constructor visibility to public during runtime and creating a new instance using that constructor, you can potentially create additional instances of the Singleton class. Run the following code to see if the Singleton class can still maintain its intended behavior.


public class SingletonTester {
  public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException,
          InvocationTargetException, InstantiationException {
      Singleton instance1 = Singleton.getInstance();

      // Using reflection to create a new instance
      Constructor constructor = Singleton.class.getDeclaredConstructor();
      constructor.setAccessible(true);
      Singleton instance2 = constructor.newInstance();

      System.out.println("Instance 1: " + instance1.hashCode());
      System.out.println("Instance 2: " + instance2.hashCode());
  }
}                     
//OUTPUT
Instance 1: 918221580
Instance 2: 681842940

//The different hash codes of the two instances indicate the failure of the Singleton class in maintaining its intended behavior.

                    

Solution: To prevent Singleton failure from reflection, throw a runtime exception in the constructor if an initialization attempt is made when it is already initialized.

public class Singleton {
  private static Singleton instance;

  private Singleton() {
      if (instance != null) {
          throw new IllegalStateException("Singleton instance already exists");
      }
  }

  public static Singleton getInstance() {
      if (instance == null) {
          instance = new Singleton();
      }
      return instance;
  }
}

//If an instance already exists, attempting to create a new instance using reflection will result in an exception being thrown.
//Caused by: java.lang.IllegalStateException: Singleton instance already exists

                    

Make Singleton thread safe

If two threads try to initialize the Singleton class at almost the same time, a race condition can occur. Let's test the code below, where two threads are created almost simultaneously and both call the getInstance() method.
  public class SingletonTester {
    public static void main(String[] args)  {
        Thread thread1 = new Thread(() -> {
            Singleton instance = Singleton.getInstance();
            System.out.println("Instance 1: " + instance.hashCode());
            // Use the instance as needed
        });

        Thread thread2 = new Thread(() -> {
            Singleton instance = Singleton.getInstance();
            System.out.println("Instance 2: " + instance.hashCode());
            // Use the instance as needed
        });
        thread1.start();
        thread2.start();
    }
}


//OUTPUT
Instance 2: 145134568
Instance 1: 931386117

Solution: To ensure thread safety and prevent multiple instances, synchronization mechanisms like locks, double-checked locking, or the volatile keyword can be used in the getInstance() method. These mechanisms guarantee that only one instance is created, even if multiple threads attempt to initialize the Singleton class simultaneously.

Make Singleton thread safe using synchronization mechanisms

  public class Singleton {
    private static Singleton instance;

    private Singleton() {
        if (instance != null) {
            throw new IllegalStateException("Singleton instance already exists");
        }
    }

    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

Make Singleton thread safe using double-checked synchronization mechanisms

  public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Make Singleton thread safe using volatile keyword and double-checked locking mechanisms

  public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Now, above Singleton class is thread safe.

Make Singleton safe from Serialization

In distributed systems, there are cases where it is necessary to implement the Serializable interface in a Singleton class. This allows the state of the Singleton to be stored in the file system and retrieved at a later point in time.

public class SingletonTester {
  public static void main(String[] args)  {
      try {
          Singleton obj1 = Singleton.getInstance();
          ObjectOutput out = null;

          out = new ObjectOutputStream(new FileOutputStream("filename.ser"));
          out.writeObject(obj1);
          out.close();

          //deserialize from file to object
          ObjectInput in = new ObjectInputStream(new FileInputStream("filename.ser"));
          Singleton obj2 = (Singleton) in.readObject();
          in.close();

          System.out.println("Instance1 hashCode=" + obj1.hashCode());
          System.out.println("Instance2 hashCode=" + obj2.hashCode());

      } catch (IOException | ClassNotFoundException e) {
          e.printStackTrace();
      }
  }
}

//OUTPUT
Instance1 hashCode=648129364
Instance2 hashCode=1717159510

Solution: By implementing the readResolve() method in the Singleton class, you can control the process of object creation during deserialization and ensure that only the existing Singleton instance is returned. This prevents the creation of multiple instances through serialization and deserialization.

public class Singleton implements Serializable {
  private static Singleton instance;

  private Singleton() {
      if (instance != null) {
          throw new IllegalStateException("Singleton instance already exists");
      }
  }

  public synchronized static Singleton getInstance() {
      if (instance == null) {
          instance = new Singleton();
      }
      return instance;
  }

  protected Object readResolve() {
      return instance;
  }
}

Make Singleton safe from cloning

Cloning is a concept used to create duplicate objects, and if we attempt to clone a Singleton object, it will create a copy, resulting in two instances of the Singleton class. This violates the Singleton pattern, as the class is intended to have only a single instance throughout its lifetime.

//Super Class
public class SuperClass implements Cloneable {
  @Override
  protected Object clone()
          throws CloneNotSupportedException{
      return super.clone();
  }
}

// Singleton class
public class Singleton extends SuperClass {
  public static Singleton instance = new Singleton();

  private Singleton()
  {
      // private constructor
  }
}

//Testing Class
public class SingletonTester {
  public static void main(String[] args) throws CloneNotSupportedException {
          Singleton instance1 = Singleton.instance;
          Singleton instance2 = (Singleton) instance1.clone();
          System.out.println("Instance1 hashCode:- "+ instance1.hashCode());
          System.out.println("Instance2 hashCode:- "+ instance2.hashCode());
  }
}

//OUTPUT
Instance1 hashCode:- 2055281021
Instance2 hashCode:- 1392838282
                  

Solution: To prevent cloning of a Singleton, override the clone() method in the Singleton class and throw a CloneNotSupportedException. This will indicate that cloning of the Singleton object is not supported and prevent the creation of a duplicate instance.

public class Singleton implements Cloneable {
  // Singleton implementation code

  @Override
  public Object clone() throws CloneNotSupportedException {
      throw new CloneNotSupportedException("Cloning of Singleton objects is not allowed.");
  }
}  

Singleton safe from reflection,thread,Serialization,cloning

  public class Singleton implements Cloneable, Serializable {
    private static volatile Singleton instance;

    private Singleton() {
        if (instance != null) {
            throw new IllegalStateException("Singleton instance already exists");
        }
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    protected Object readResolve() {
        return instance;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException("Cloning of Singleton objects is not allowed.");
    }
}

private static volatile Singleton instance;: Declaration of the volatile static variable instance to hold the Singleton instance. The volatile keyword ensures visibility and prevents possible issues with multithreaded access.

private Singleton(): Private constructor to prevent external instantiation of the Singleton class.

public static Singleton getInstance(): Public static method that returns the Singleton instance. It implements the double-checked locking mechanism and ensures thread safety.

protected Object readResolve(): The readResolve() method is called during deserialization and ensures that the same instance is returned, preserving the Singleton pattern.

@Override public Object clone() throws CloneNotSupportedException: The clone() method is overridden to prevent cloning of Singleton objects. It throws a CloneNotSupportedException to indicate that cloning is not allowed.

SingletonTest Class

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class SingletonTester {
    public static void main(String[] args) {
        // Get the Singleton instance
        Singleton instance1 = Singleton.getInstance();
        System.out.println("Instance 1 hash code: " + instance1.hashCode());

        // Breaking Option 1: Cloning
        try {
            Singleton instance2 = (Singleton) instance1.clone();
            System.out.println("Instance 2 hash code (Cloning): " + instance2.hashCode());
        } catch (CloneNotSupportedException e) {
            System.out.println(e.getMessage());
        }

        // Breaking Option 2: Serialization and Deserialization
        try {
            // Serialize the Singleton instance
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
            out.writeObject(instance1);
            out.close();

            // Deserialize the Singleton instance
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("singleton.ser"));
            Singleton instance3 = (Singleton) in.readObject();
            in.close();

            System.out.println("Instance 3 hash code (Serialization/Deserialization): " + instance3.hashCode());
        } catch (IOException | ClassNotFoundException e) {
            System.out.println(e.getMessage());
        }

        // Breaking Option 3: Reflection
        try {
            Class singletonClass = Class.forName("Singleton");
            Constructor constructor = singletonClass.getDeclaredConstructor();
            constructor.setAccessible(true);
            Singleton instance4 = (Singleton) constructor.newInstance();
            System.out.println("Instance 4 hash code (Reflection): " + instance4.hashCode());
        } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
                  IllegalAccessException | InvocationTargetException e) {
            System.out.println(e.getMessage());
        }
        // Breaking Option 4: Thread

        Thread thread1 = new Thread(() -> {
            Singleton instance = Singleton.getInstance();
            System.out.println("Instance 5: " + instance.hashCode());
            // Use the instance as needed
        });

        Thread thread2 = new Thread(() -> {
            Singleton instance = Singleton.getInstance();
            System.out.println("Instance 6: " + instance.hashCode());
            // Use the instance as needed
        });
        thread1.start();
        thread2.start();
    }
}
                    

//OUTPUT 
Instance 1 hash code: 918221580
Cloning of Singleton objects is not allowed.
Instance 3 hash code (Serialization/Deserialization): 918221580
null
Instance 5: 918221580
Instance 6: 918221580
                  

Cloning:We attempt to clone the Singleton instance using the clone() method. As expected, a CloneNotSupportedException is thrown to indicate that cloning of Singleton objects is not allowed.
Serialization and Deserialization:We serialize the Singleton instance to a file and then deserialize it back into a new instance. However, the readResolve() method in the Singleton class ensures that the same instance is returned during deserialization, maintaining the Singleton behavior.
Reflection:We use reflection to access the private constructor of the Singleton class, create a new instance, and obtain its hash code. However, the code throws an exception (NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException) to indicate that creating another instance through reflection is not allowed.
Multiple Threads: We create two threads (thread1 and thread2) that simultaneously call the getInstance() method of the Singleton class. The output will demonstrate whether the Singleton pattern remains intact and only one instance is created, even in a multi-threaded environment.
By testing these breaking options, we can verify that the Singleton class maintains its intended behavior and prevents the creation of multiple instances.

Conclusion

In conclusion, the implemented Singleton pattern provides thread safety, prevents instantiation through reflection, disallows cloning, and maintains consistency during serialization. While it may not be perfect, for most applications, this implementation is effective and ensures the desired Singleton behavior. Consider the specific requirements of your application when deciding to use the Singleton pattern.

  • #Technology
  • #Java
  • Read

    Hey Wait...

    Subscribe to our newsletter and never miss our latest blogs and news, etc.

    Our newsletter is send once a week, every Monday.

    We respect your privacy.No spam ever!

    Take a look at my resume

    Contact Me!

    Have a question or just want to Get in Touch?

    Phone: +91 9758884230

    Email: shyambhati97@gmail.com

    Address: Greater Noida, Gautam Budh Nagar, 203202, India