Creational - Singleton

Multiple ways to implement singleton design pattern, but what should you use?

Imagine there’s only one cookie jar in your house. No matter how many times you ask for a cookie, your parents always send you to the same jar—the ONE and ONLY COOKIE JAR. If all your friends come over, everyone has to share from the same jar too. No matter what, there’s just one magic cookie jar!

If anyone tries to sneak a second cookie jar into the house… the cookie police (your parents!) say “Nope! Only one allowed!”

So, a singleton in computer code is just like this: there can never be two cookie jars, and everyone always gets cookies from the one special jar!

Singleton is simply a class which is instatiated once per JVM initialisation.There are many ways to achieve it.

Eager initialisation

public class EagerSingleton {
	// instance created immediately
	private static volatile EagerSingleton instance = new EagerSingleton();

	// private constructor
	private EagerSingleton() {
	}

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

Advantage:

  • Rely on JVM to create singleton instance

  • JVM guaraentees that the instance will be created before any thread access the instance variable.

  • inherently thread-safe

Drawback:

  • Instance is created irrespective of it is required in runtime or not

  • This wastes resources

    • if singleton instance creation is a heavy process

    • or instance is never used by the client application

Then what's the better approach ?

Lazy Initialization

Lazy initialization is a concept where the Singleton instance is not created until it is needed (on the first call to getInstance()), rather than at class loading time.

public final class LazySingleton {
	private static volatile LazySingleton instance = null;

	// private constructor
	private LazySingleton() {
	}

	public static LazySingleton getInstance() {
		if (instance == null) {  //T1 and T2 at same time
			synchronized (LazySingleton.class) { // suppose T1 enters, then T2
				instance = new LazySingleton(); // inst1, inst2
			}
		}
		return instance;
	}
}

Drawback:

Suppose there are two threads T1 and T2. Both comes to create instance and execute instance==null, now both threads have identified instance variable to null thus assume they must create an instance. They sequentially goes to synchronized block and create the instances. At the end, we have two instances in our application.

Then what's the solution?

Double Checked Locking

public class LazySingleton {
	private static volatile LazySingleton instance = null;

	// private constructor
	private LazySingleton() {
	}

	public static LazySingleton getInstance() {
		if (instance == null) { // Both T1 and T2 at same time
			synchronized (LazySingleton.class) { // Suppose T1 enters first
				// Double check 
				if (instance == null) { 
					instance = new LazySingleton(); // inst1
				}
			} // Turn for T2
		}
		return instance;
	}
}

Please ensure to use Volatile keyword with instance variable otherwise you can run into out of order write error scenario, where reference of instance is returned before actually the object is constructed i.e. JVM has only allocated the memory and constructor code is still not executed. In this case, your other thread, which refer to uninitialized object may throw null pointer exception and can even crash the whole application.

Though it's a popular solution, but there is an alternative approach to get rid of synchronization overhead.

Bill pugh Solution

// Instead of intializing immediately, 
//instance will be created using an inner class
public class BillPughSingleton {
	// private constructor
	private BillPughSingleton() {}
	
	// inner class
	private static class LazyHolder {
		private static final BillPughSingleton INSTANCE = new BillPughSingleton();
	}

	public static BillPughSingletongetInstance() {
		return LazyHolder.INSTANCE;
	}
}

Advantage:

  • Lazy Initialization : As you can see, until we need an instance, the LazyHolder class will not be initialized until required and you can still use other static members of BillPughSingleton class.

  • Thread Safety : This method is inherently thread-safe without requiring synchronized blocks.

Class loading in Java is thread-safe by default, so the instance creation is safe in multithreaded environments.

  • No Synchronization Overhead: There is no performance cost of synchronization in the BillPughSingletongetInstance() method.

Now, it seems, we are good with singleton pattern using Lazy Initialization. If you think so, then check below code

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;

public class ClientHackerOne {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        createMultipleInstanceUsingSerialization();
    }
    // similar code for bill pugh solution as well
    private static void createMultipleInstanceUsingSerialization() throws IOException, ClassNotFoundException {
        DLazySingleton instance1 = DLazySingleton.getInstance();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(Files.newOutputStream(Paths.get("tmp.obj")));
        objectOutputStream.writeObject(instance1);
        objectOutputStream.close();

        ObjectInputStream objectInputStream = new ObjectInputStream(Files.newInputStream(Paths.get("tmp.obj")));
        DLazySingleton instance2 = (DLazySingleton) objectInputStream.readObject();
        objectInputStream.close();

        System.out.println("Object1 : " + instance1.hashCode()); //1775282465
        System.out.println("Object2 : " + instance2.hashCode()); //1604839423

        File file = new File ("tmp.obj");
        file.deleteOnExit();

    }
}

You can see, the hashcode of the objects are different. This means the objects are different. Hence Singleton pattern is violated.

To solve this, use implement readResolve() method in LazySingletonClass

//inside Singleton class
protected Object readResolve() {
    return instance;
}
//This method ensures that during deserialization, 
//the existing singleton instance is returned rather than creating a new object.

Now you can run ClientHackerOne class to check the hashcodes of the objects.

Now, I think, we are good enough for Singleton Pattern.

Wait!!! Check below class

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

public class ClientHackerTwo {
    public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException {
        createMultipleInstanceUsingReflection();
    }

    private static void createMultipleInstanceUsingReflection() throws InvocationTargetException, InstantiationException, IllegalAccessException {
        Constructor<?>[] declaredConstructors = DLazySingleton.class.getDeclaredConstructors();
        Constructor<?> constructor = declaredConstructors[0];
        constructor.setAccessible(true);

        DLazySingleton instance1 = (DLazySingleton) constructor.newInstance();
        DLazySingleton instance2 = DLazySingleton.getInstance();

        System.out.println("Object1 : " + instance1.hashCode()); //1067040082
        System.out.println("Object2 : " + instance2.hashCode()); //1706377736

        
    }
}

Again, the objects are with different hashcodes.

To solve this use Enum

Enum

provide implicit support for thread safety and only one instance is guaranteed.Recommended in Effective Java

public enum EnumSingleton {
	INSTANCE;
	public void someMethod(String param) {
		// some class member
	}
}

To Test the singleton pattern use below code in ClientHackerTwo

EnumSingleton singleton1 = EnumSingleton.INSTANCE;
EnumSingleton singleton2 = EnumSingleton.INSTANCE;

System.out.println("Object1 : " + singleton1.hashCode()); //1804094807 
System.out.println("Object2 : " + singleton2.hashCode()); //1804094807

A classic example of the singleton pattern in the JDK is the java.lang.Runtime class.

  • Only one Runtime object can exist in a Java application; you can’t create another instance.

  • You get the single instance by calling Runtime.getRuntime().

  • This object lets you interact with the environment in which the application is running (like running external processes, reading environment variables, etc.).

Runtime runtime = Runtime.getRuntime();

Conclusion

Singleton Pattern can be achieved by 3 ways

  • Eager Initialization - JVM way, thread safe

  • Lazy Initialization - Lazily initialized,

    • go for double checking approach with readResolve()

    • or go for bill pugh solution with readResolve()

  • Enum for simple usecases (recommended)

Last updated

Was this helpful?