StackTips

Implementing Singleton Design Pattern in Java

nilan avtar

Written by

Nilanchala,  20 min read,  4.23K views, updated on Sept. 29, 2024

Singleton design pattern belongs to the creational family of patterns that governs the object instantiation process. It ensures at most one instance of a class is ever created throughout the lifecycle of your application.

Here are some of the real-time use cases of the singleton class:

  • Project Configuration: Project configurations are read and loaded into memory once and used multiple times throughout the application lifecycle.
  • Application Log: The Logger object will be used globally everywhere in your application. It must be initialized once and used everywhere.
  • Analytics and Reporting: If you are using some kind of data Analytics or tracking service, then, you must design them to be singleton so that they are initialized once and used everywhere.

Notice that the Singleton class has a private static instance variable named instance. The default constructor of the Singleton class is made private to prevent other classes from instantiating it.

The getInstance() method is declared in the static scope to make the technique accessible globally. The getInstance() method returns the singleton object.

Implementing the Singleton Class

To declare a class as singleton, you have to:

  • The default constructor must be private. A private constructor prevents the direct instantiation of the object from other classes.
  • Create a public static getInstance() method. A member declared as static can be accessed without creating an object using the new operator. This method returns the instance of the singleton class.
  • Lazy initialization is preferable to create an object on first use.

Singleton with Lazy Initialization

Here's how we can create a singleton class using lazy initialization

class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

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

    public void method1() {
        System.out.println("Hurray! I am method1 from Singleton!");
    }
}

Let us now test the singleton initialization:

Singleton object = new Singleton();
object.method1();

The above code will result in a compilation error. As the default constructor is made private, you are not allowed to create instances using a new keyword. However, we can use the getInstance() method to get the instance of the Singleton class.

Singleton object = Singleton.getInstance();
object.method1();

This is how we create a Singleton class using lazy initialization. The getInstance() method instantiates the class for the first use.

Singleton and Thread Safety

Notice that the getInstance() method is not thread-safe. Two instances of the Singleton class will be created if the getInstance() is called simultaneously by two threads.

This issue can be avoided by making the getInstance() method synchronized. This way we force every thread to wait for its turn before it executes. i.e. no two threads can be entered into the getInstance() method at the same time.

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

The above implementation will answer the thread safety problem. However, the synchronized methods are expensive and can have a serious performance hit.

We can optimize this using the synchronization block.

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

Singleton with Early Initialization

Using early initialization we will initialize upfront before your class is loaded. This way you don’t need to check for synchronization as it is initialized before being used ever.

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

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }

    public void method1() {
        System.out.println("Hurray! I am method1 from Singleton!");
    }
}

Singleton and Object Cloning

Java allows you to create a copy of the object with similar attributes and state from the original object using cloning. To implement cloning, we have to implement java.lang.Cloneable interface and override clone() method from the Object class.

It is a good idea to prevent cloning in a singleton class.

To disallow cloning in your singleton class, we can override the clone method and explicitly throw the CloneNotSupportedException exception.

class Singleton implements Cloneable {
    private static Singleton instance;

    private Singleton() {
    }

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

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

    public void method1() {
        System.out.println("Hurray! I am method1 from Singleton!");
    }
}

Singleton and Serialization

Serialization in Java allows to conversion of the state of an object into a stream of bytes so that it can easily be stored into a file or transferred via network. Once the object is serialized, you can deserialize it, back to the object from the byte stream.

If a singleton class is meant to be serialized, it will end up creating duplicate objects. Let us have a look at the example below,

import java.io.Serializable;

class Singleton implements Cloneable, Serializable {
    private static final long serialVersionUID = 1L;
    private static Singleton instance;
    private int value;

    private Singleton() {
    }

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

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

    public void method1() {
        System.out.println("Hurray! I am method1 from Singleton!");
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

In this example, the Singleton class is implementing the java.io.Serializable interface, which means the state of its object can be persisted.

Now, let us save the object into a file and then retrieve it later.

public static void main(String[] args) {
    Singleton instanceOne = Singleton.getInstance();
    instanceOne.setValue(10);

    try {
        FileOutputStream fos = new FileOutputStream("filename.txt")
        ObjectOutput out = new ObjectOutputStream(fos);
        out.writeObject(instanceOne);
        out.close();
        instanceOne.setValue(20);

        FileInputStream fis = new FileInputStream("filename.txt")
        ObjectInput in = new ObjectInputStream(fis);
        Singleton instanceTwo = (Singleton) in.readObject();
        in.close();

        System.out.println("instanceOne:" + instanceOne.getValue());
        System.out.println("instanceTwo:" + instanceTwo.getValue());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

This will print different values, which means that they are two different objects. Here we are violating the singleton principle.

To solve this issue, we need to include the readResolve() method in our Singleton class. This method will be invoked before the object is deserialized and here we will call the getInstance() method to return the same object after deserialization.

class Singleton implements Cloneable, Serializable {
    private static final long serialVersionUID = 1L;
    private static Singleton instance;
    private int value;

    private Singleton() {
    }

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

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

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

    public void method1() {
        System.out.println("Hurray! I am method1 from Singleton!");
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

Noteworthy

  • Singleton classes are used sparingly. Do not think of this pattern, unless you know what you are doing. As the object is created in the global scope, this is riskier in resource-constrained platforms.
  • Beware of object cloning. Double-check and block the object’s clone method
  • Careful when multiple threads access the singleton class
  • Careful of multiple class loaders they can break your singleton
  • Implement strict type if your singleton class is serialized

Practice Question

Design a Rate Limiter class that limits the number of requests an API can receive within a given time frame. The Rate Limiter should be implemented as a Singleton to ensure that only one instance is used throughout the system.

Hint:

  • RateLimiter Initializes the rate limiter object. Each user is allowed a maximum of 3 requests per minute.
  • Create a method `allowRequest(int timestamp, int userId)`  that returns true if the request from the given userId is allowed at the given timestamp, otherwise returns false.

Explain using the Java Scanner library to accept user input for the user ID, automatically generate a timestamp, and interact with our RateLimiter class. The main method will print "Hello" if the request from the user is accepted.

    nilan avtar

    Nilanchala

    I'm a blogger, educator and a full stack developer. Mainly focused on Java, Spring and Micro-service architecture. I love to learn, code, make and break things.

    Related posts

    Let’s be friends!

    🙌 Stay connected with us on social media for the latest updates, exclusive content, and more. Follow us now and be part of the conversation!