Java

What is Singleton Design Pattern?


Spring Datafication 2022. 11. 10. 23:28

Singleton design pattern is one of the most used design patterns in Java. This type of design pattern comes under creational pattern as this pattern provides one of the best ways to create an object.

This pattern involves a SINGLE CLASS which is responsible to create an object while making sure that ONLY SINGLE OBJECT gets created. This class provides a way to access its only object which can be accessed directly without need to instantiate the object of the class.

Implementation

We're going to create a App class having its constructor as private and a static method to get its object. Logger class provides a way to access its only object which can be accessed directly without need to instantiate the object of the class.

Logger.java

    public class Logger {
        private static Logger logger = null;
        private Logger() {
        }
        public static Logger getInstance() {
            if (logger == null) {
                logger = new Logger();
            }
            return logger;
        }
        public void log(String message) {
            System.out.println(message);
        }
    }

Advantages of Singleton Design Pattern

  • Single Responsibility - The single responsibility principle states that a class should have
  • Single Point of Access or Permits Controlled Access - There is only one instance of the class, so there is only one point of access to it.
  • Controlled Access and Reduced Name Space - The class controls the access to the single instance, so it can decide when and how to instantiate the instance.

Disadvantages of Singleton Design Pattern

  • Singletons are Global - The single instance is global to the class, so it is not possible to subclass the class with a local instance.
  • Hard to Test or Change- The class is difficult to test because the class is tightly coupled to the single instance.

How Many Ways to Implement Singleton Design Pattern in Java?

LAZY SINGLETON

public class LazyAlfrescoSingleton implements Serializable {
    private static LazyAlfrescoSingleton instance = null;

    //prevent instantiation from other classes
    private LazyAlfrescoSingleton() {
    }

    // a static method to get the instance
    public static LazyAlfrescoSingleton getInstance() {
        if (instance == null) {
            instance = new LazyAlfrescoSingleton();
        }
        return instance;
    }

    // prevent cloning
    protected Object readResolve() {
        return instance;
    }
}

Disadvantages of Lazy Singleton

The above implementation works fine in case of single threaded environment but when it comes to multithreaded systems, it can cause issues if multiple threads are inside the if condition at the same time. It will destroy the singleton pattern and both threads will get the different instances of singleton class.

/* Thread 1 enters the if block*/
if (instance == null) {
    // Thread 2 enters the if block
    if (instance == null) {
        instance = new LazyAlfrescoSingleton();
    }
}

EAGER SINGLETON

From the start of the JVM is loaded, the instance of the singleton class is created. This is the easiest method to create a singleton class but it has a drawback that instance is created even though client application might not be using it.

public class EagerAlfrescoSingleton implements Serializable {
    private static EagerAlfrescoSingleton instance = new EagerAlfrescoSingleton();

    //prevent instantiation from other classes
    private EagerAlfrescoSingleton() {
    }

    // a static method to get the instance
    public static EagerAlfrescoSingleton getInstance() {
        return instance;
    }

    // prevent cloning
    protected Object readResolve() {
        return instance;
    }
}

THREAD SAFE SINGLETON

The above implementation works fine and provides thread-safety but it reduces the performance because of synchronized method, although we need it only for the first few threads who might create the separate instances (Read: Java Synchronization).

public class ThreadSafeAlfrescoSingleton implements Serializable {
    private static ThreadSafeAlfrescoSingleton instance = null;

    //prevent instantiation from other classes
    private ThreadSafeAlfrescoSingleton() {
    }

    // a static method to get the instance
    public static synchronized ThreadSafeAlfrescoSingleton getInstance() {
        if (instance == null) {
            instance = new ThreadSafeAlfrescoSingleton();
        }
        return instance;
    }

    // prevent cloning
    protected Object readResolve() {
        return instance;
    }
}

DOUBLE CHECKED LOCKING SINGLETON

synchronized Blocks, Is used to reduce the overhead of method-level synchronization, but it does not work prior to Java 5. If multiple threads can access the singleton class simultaneously, it can cause issues as well. So double checked locking principle is used to solve this issue.

public class DoubleCheckedLockingAlfrescoSingleton implements Serializable {
    private static DoubleCheckedLockingAlfrescoSingleton instance = null;

    //prevent instantiation from other classes
    private DoubleCheckedLockingAlfrescoSingleton() {
    }

    // a static method to get the instance
    public static DoubleCheckedLockingAlfrescoSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedLockingAlfrescoSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedLockingAlfrescoSingleton();
                }
            }
        }
        return instance;
    }

    // readResolve method to prevent creating new instance while deserializing
    //return the instance created during the class loading
    protected Object readResolve() {
        return instance;
    }
}

NOTICE: The above is best in multi-threaded environment but it can cause issues in multi-core environment as well. So it is recommended to use Bill Pugh Singleton Implementation for Java projects.

BILL PUGH SINGLETON

Before Java 5, java memory model had a lot of issues and above approaches used to fail in certain scenarios where too many threads try to get the instance of the Singleton class simultaneously. So Bill Pugh came up with a different approach to create the Singleton class using a static inner helper class.

public class BillPughAlfrescoSingleton implements Serializable {
    private BillPughAlfrescoSingleton() {
    }

    private static class SingletonHelper {
        private static final BillPughAlfrescoSingleton INSTANCE = new BillPughAlfrescoSingleton();
    }

    public static BillPughAlfrescoSingleton getInstance() {
        return SingletonHelper.INSTANCE;
    }

    // This is the key method which is responsible during serialization and deserialization
    // This method get invoked, and we are simple returning the already created instance
    protected Object readResolve() {
        return getInstance();
    }
}

How To Break Singleton Design Pattern in Java?

  • Reflection - Reflection can be used to break the singleton pattern by calling the private constructor using Reflection API.
  • Serialization - Serialization can be used to break the singleton pattern by calling the readResolve() method.
  • Cloning - Cloning can be used to break the singleton pattern by calling the clone() method.

    Serialization Break of Singleton

import com.fas.fcdsystem.singletonDP.LazyAlfrescoSingleton;

import java.io.ObjectOutputStream;

public class SerializationBreakAlfrescoSingleton implements Serializable {
    LazyAlfrescoSingleton instance = LazyAlfrescoSingleton.getInstance();
    ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("fas.obj"));
    oos.writeObject(instance);
    oos.close();

    // read object from file
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("fas.obj"));
    LazyAlfrescoSingleton instance1 = (LazyAlfrescoSingleton) ois.readObject();
    ois.close();

    System.out.println("instance hashCode="+instance.hashCode());
    System.out.println("instance1 hashCode="+instance1.hashCode());
}

Output without readResolve() method

instance hashCode=1163157884
instance1 hashCode=1956725890

Output with readResolve() method

instance hashCode=1163157884
instance1 hashCode=1163157884

Reflection Break of Singleton

import com.fas.fcdsystem.singletonDP.LazyAlfrescoSingleton;

import java.lang.reflect.Constructor;

public class ReflectionBreakAlfrescoSingleton {
    LazyAlfrescoSingleton instanceOne = LazyAlfrescoSingleton.getInstance();
    Constructor[] constructors = LazyAlfrescoSingleton.class.getDeclaredConstructors();
    for (Constructor constructor : constructors) {
        // Below code will destroy the singleton pattern
        constructor.setAccessible(true);
        instanceTwo = (LazyAlfrescoSingleton) constructor.newInstance();
        break;
    }
    System.out.println("instanceOne hashCode="+instanceOne.hashCode());
    System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());
}

Output without readResolve() method

instanceOne hashCode=3125656
instanceTwo hashCode=2312424

Output with readResolve() method

instanceOne hashCode=2055036994
instanceTwo hashCode=2055036994

How to prevent Singleton Design Pattern from being broken in Java?

  • Reflection - To prevent singleton pattern from being broken by Reflection, we can throw an exception in the private constructor.
  • Serialization - To prevent singleton pattern from being broken by Serialization, we can implement the readResolve() method.
  • Cloning - To prevent singleton pattern from being broken by Cloning, we can throw an exception in the clone() method.
  • Enum - Enum is the best way to create a singleton class as it is thread-safe and prevents from Reflection API.

REFERENCES

반응형