Singleton Design Pattern

In this article of Java design patterns, we will continue our learning of design patterns and cover the Singleton Design Pattern. We will understand what this pattern is about using a Java application. We will then see the design benefits of the Singleton Design Pattern.

Singleton Design Pattern

The Singleton Design Pattern is a part of the creational design pattern that ensures that a class has one and only one instance per JVM. It provides global access to this instance for any user in this class. Implementing Java Singleton pattern has always been a controversial topic among developers.

Singleton Design Pattern solves a couple of problems. It also violates the Single Responsibility Principle. Let’s look at some problems solved by singleton pattern.

  1. Singleton Design Pattern ensures that there is only one instance for this class. This is very useful with shared resources.
  2. Singleton Design Pattern provides global access to the instances and also protects the instance from being corrupted/overwritten by other codes.

Most times, people also call this pattern as singleton as short form.

1. Implementing Singleton Pattern

To understand it better, let’s implement the Singleton Design Pattern in Java. There are 2 major use cases while we are implementing this design pattern.

  • Single-Threaded environment.
  • Multi-Threaded environment.

2. Singleton Pattern with Single-Threaded Environment

For single-threaded applications, we will have to do:

  • Create a private static instance of the Singleton class.
  • Keep the constructor private.
  • Create a private static getInstance() method to return the same object always.
public class Singleton {
    
    /* private instance variable */
    private static Singleton instance = new Singleton();

    /* private constructor */
    private Singleton() {}

    /* returns the same object */
    public static Singleton getInstance() {
        return instance;
    }
}

2.1. Breaking Singleton Using Reflection

We can break the Singleton pattern using the reflection using the below code. We should be careful while using this pattern, especially with reflection.

public class BreakSingletonUsingReflection {
    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = null;
        try {
            Constructor[] constructors = Singleton.class.getDeclaredConstructors();
            for (Constructor constructor: constructors) {
                constructor.setAccessible(true);
                singleton2 = (Singleton) constructor.newInstance();
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("instance1.hashCode(): " + singleton1.hashCode());
        System.out.println("instance2.hashCode(): " + singleton2.hashCode());
    }
}

If you run this program, this is what we will see as output:

instance1.hashCode(): 1163157884
instance2.hashCode(): 1956725890

From Java 5, it is best to use Enum for creating singleton objects to ensure that Reflection can’t break it. We haven’t covered the ways to break the code using Cloneable and Serializable however those are easy to implement as per below:

Cloneable -> Throw exception from inside of clone() method

@Override
protected Object clone() throws CloneNotSupportedException{
    throw new CloneNotSupportedException();
}

Serializable -> use the readResolve() method to avoid the creation of a new object while we de-serialize the object

protected Object readResolve(){
    return instance;
}

3. Singleton Design Pattern with Multi-Threaded Environment

For the multi-threaded environment, we need to be extra careful as two or more threads can call the object creation and in that we will have over one object created for Singleton and hence defeating the purpose of the Singleton pattern.To overcome this issue, we are going to use double-check locking for a multi-threaded environment and as you will later, all the threads will have the same copy of the object.

public class SingletonMultiThreaded {

    /* private instance variable  */
    private static volatile SingletonMultiThreaded INSTANCE;

    /* private constructor */
    private SingletonMultiThreaded() {}

    public static SingletonMultiThreaded getInstance() {
        /* double-checking lock */
        if (null == INSTANCE) {
            /* synchronized block */
            synchronized(SingletonMultiThreaded.class) {
                if (null == INSTANCE) {
                    INSTANCE = new SingletonMultiThreaded();
                }
            }
        }
        return INSTANCE;
    }
}

Thread1:

public class Thread1 implements Runnable {
    @Override
    public void run() {
        SingletonMultiThreaded singletonMultiThreaded = SingletonMultiThreaded.getInstance();
        System.out.print(singletonMultiThreaded.hashCode() + " ");
    }
}

Thread2:

public class Thread2 implements Runnable {
    @Override
    public void run() {
        SingletonMultiThreaded singletonMultiThreaded = SingletonMultiThreaded.getInstance();
        System.out.print(singletonMultiThreaded.hashCode() + " ");
    }
}

Thread3:

public class Thread3 implements Runnable {
    @Override
    public void run() {
        SingletonMultiThreaded singletonMultiThreaded = SingletonMultiThreaded.getInstance();
        System.out.println(singletonMultiThreaded.hashCode());
    }
}

Do remember that testing a Singleton class in a thread-safe environment is not that straightforward due to race conditions and timing on the treads taking the lock on the critical section of the code where we are checking the nullability of the instance.

4. Client Program

Let see the sample client program for better understanding:

public class SingletonPatternDemo {
    public static void main(String[] args) {
        /* Let's create 3 objects and see their hashcode and they will be same. */
        System.out.println("in single-threaded environment");
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = Singleton.getInstance();
        Singleton singleton3 = Singleton.getInstance();
        System.out.println(singleton1.hashCode() + " " + singleton2.hashCode() + " " + singleton3.hashCode());

        System.out.println("in multi-threaded environment");
        Thread1 t1 = new Thread1();
        t1.run();
        Thread2 t2 = new Thread2();
        t2.run();
        Thread3 t3 = new Thread3();
        t3.run();

    }
}

4.1. Code Output

Let’s see the output of the client program for both single-threaded and multi-threaded programs, in both cases we are creating 3 different objects however as you can see our Singleton and SingletonMultiThreaded classes are returning the same hashcode (same object) for those 3 objects, respectively. 

in a single-threaded environment
1163157884 1163157884 1163157884
In a multi-threaded environment
1956725890 1956725890 1956725890

Process finished with exit code 0

5. Singleton Design Pattern – Real-World Examples

Here is the list of some of the known real world example using the singleton pattern:

  • Logger:- In enterprise applications, the Logger object is a real-world example of a Singleton pattern. If this class is not a singleton, there will be a new log file created for every user call (client applications). All the logs will be written in a single file using this design.
  • Cache:- To have a global point of reference, it can also create the cache classes as Singleton objects. Once again, in real-life client applications will interact with the same cache objects for cache entries.
  • Configuration File:- In enterprise applications, the configuration/properties can be based on the Singleton pattern. With this, multiple client applications will use the same files (that’s the idea anyway, that configuration mostly remains the same for all the clients).

In all the above examples, it will create the Singleton instance for the first time, and thereafter from the second call on-wards, the client applications use the same instance.Here is the class diagram to understand the pattern:

Singleton Design Pattern
Singleton Design Pattern

6. Pros and Cons

There are multiple advantages of this pattern. Let’s see a few of these:

  1. Instance Control: This pattern ensures everyone accesses the same instance and no one can overwrite it or create new instances.
  2. Flexibility: Since the Singleton class controls the instantiation of the instance, it can change the behavior whenever it wants.
  3. It is very useful a in multi-threaded pool and we want to manage the resources that are limited in capacity.

6.1. Cons

There are few disadvantages to using this design pattern.

  1. violates the Single Responsibility Principle, as this solves 2 problems at the same time.
  2. It can mask poor designs.
  3. This can add quite challenges in a multi-threaded environment.

7. When to Use It?

  1. We should use the Singleton Design Pattern when we want to share a single object among multiple users/callers. For example, a database connection.
  2. We should use the Singleton Design Pattern when we want stricter control over global variables. Remember, no one except the Singleton class itself can replace the cached instance.

7.1. JDK / Spring Using Singleton Pattern

  1. java.lang.Runtime#getRuntime()
  2. java.awt.Desktop#getDesktop()
  3. java.lang.System#getSecurityManager()
  4. Spring Bean Scope

8. Alternate Implementations:

Let’s see some of the other popular implementations:

8.1 Enum Singleton

To overcome this situation with Reflection, we can use the Enum to implement Singleton design pattern. Java ensures any enum value is instantiated only once in a Java program.

package com.javadevjournal.singleton;

public enum EnumSingleton {

    INSTANCE;

    public static void work() {
        //do something
    }
}

8.2. Singleton Pattern with Serialization

In certain case, you may be forced to implement Serializable interface in Singleton class, especially in the distributed systems. Whenever we de-serialize a class, it creates a new instance of the class. This can be challenging, especially with Singleton pattern.

import java.io.Serializable;

public class SingletonWithSerialized implements Serializable {

    private static final long serialVersionUID = -xxxxxx;

    private SingletonWithSerialized() {}

    private static class Helper {
        private static final SingletonWithSerialized instance = new SingletonWithSerialized();
    }

    public static SingletonWithSerialized getInstance() {
        return SingletonWithSerialized.instance;
    }
}

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class SerializedSingletonTest {

    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        SingletonWithSerialized instanceOne = SingletonWithSerialized.getInstance();
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
                "test.ser"));
        out.writeObject(instanceOne);
        out.close();
        //deserailize from file to object
        ObjectInput in = new ObjectInputStream(new FileInputStream("test.ser"));
        SingletonWithSerialized instanceTwo = (SingletonWithSerialized) in.readObject();
        in.close();
        
        System.out.println("instanceOne-- "+instanceOne.hashCode());
        System.out.println("instanceTwo-- "+instanceTwo.hashCode());
    }
}

//output
instanceOne--      1011114567
instanceTwo--      1095644325

This causes issue as we are getting a new instance and defeating the purpose of the Singleton. We can handle this by implementing the readResolve().

protected Object readResolve() {
    return getInstance();
}

Summary

In this article, we look at the Singleton Design Pattern with its use cases. We covered some of the benefit and drawbacks of using this design pattern. In the last part, we talked about when to use this design pattern in the real-life scenario.