Singleton Design Pattern with Java Enum

The Singleton design pattern ensures that a class has only one instance and provides a global point of access to it. This is particularly useful when you want to control access to shared resources, such as database connections or configuration settings. In this blog, we will discuss how to create a Singleton class in Java, the potential pitfalls, and the solutions to overcome them.

Creating a Singleton Class

Step 1: create a private static instance

Step 2: private constructor to prevent instantiation

Step 3: static factory method with double Lock for thread safety

public class Singleton {
    private static volatile Singleton instance;

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

    // Static factory method with double lock
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Potential Pitfalls

While the above implementation works well, there are several ways to break the Singleton pattern:

  1. Reflection: It can be used to access the private constructor.

  2. Deserialization: When an instance is deserialized, a new instance can be created.

  3. Clone: Cloning the singleton instance can lead to multiple instances.

Solutions

To ensure that the singleton behavior is preserved, you can use the following approaches:

WAY 1: Manual Prevention

You can write code manually to prevent each of the possibilities:

  1. Reflection: Throw an exception in the constructor if an instance already exists.

  2. Deserialization: Implement the readResolve method.

  3. Clone: Override the clone method to throw a CloneNotSupportedException.

Here’s how you can implement these solutions:

import java.io.ObjectStreamException;
import java.io.Serializable;

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

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

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

    protected Object readResolve() throws ObjectStreamException {
        return instance;
    }

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

WAY 2: Use ENUM

A simpler and more effective way to create a singleton class in Java is to use an Enum. Enums inherently handle serialization and prevent instantiation through reflection.

Here’s how you can implement a singleton using an Enum:

public enum SingletonEnum {
    INSTANCE;
    public void method() {
        System.out.println("SingletonEnum : method () is called");
    }
}

Test SingletonEnum

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
public class Driver {
    public static void main(String[] args) {
        // Access the singleton instance
        SingletonEnum instance1 = SingletonEnum.INSTANCE;
        SingletonEnum instance2 = SingletonEnum.INSTANCE;

        System.out.println("instance1 == instance2 => " + (instance1==instance2));
        // Call the method
        instance1.method();
        instance2.method();

        // Test reflection
        testReflection();

        // Test serialization and deserialization
        testSerialization(instance1);

        // Test cloning
        testCloning(instance1);
    }

    private static void testReflection() {
        try {
            Constructor<SingletonEnum> constructor = SingletonEnum.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            SingletonEnum instance3 = constructor.newInstance(); // Attempt to create a new instance
            System.out.println("Reflection: Are instance1 and instance3 the same? " + (SingletonEnum.INSTANCE == instance3));
        } catch (Exception e) {
            System.out.println("Reflection: Attempt failed: " + e);
        }
    }
    private static void testSerialization(SingletonEnum instance) {
        try {
            // Serialize the instance
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(instance);
            oos.flush();
            oos.close();

            // Deserialize the instance
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            SingletonEnum instance4 = (SingletonEnum) ois.readObject();

            System.out.println("Serialization: Are instance and instance4 the same? " + (instance == instance4));
        } catch (Exception e) {
            System.out.println("Serialization: Failed: " + e.getMessage());
        }
    }
    private static void testCloning(SingletonEnum instance) {
        System.out.println("clone() has protected access in java.lang.Enum so Cloning test is not applicable for enums");
    }
}

output:

instance1 == instance2 => true
SingletonEnum : method () is called
SingletonEnum : method () is called
Reflection: Attempt failed: java.lang.NoSuchMethodException: com.hk.corejava.SingletonEnum.<init>()
Serialization: Are instance and instance4 the same? true
clone() has protected access in java.lang.Enum so Cloning test is not applicable for enums

Conclusion

In conclusion, the Singleton pattern is a powerful design pattern that can be implemented in various ways. While the traditional method using a private constructor and static factory method can be effective, it requires additional code to handle potential pitfalls. Using an Enum is a more elegant solution that simplifies the implementation and inherently protects against reflection and serialization issues.

Did you find this article valuable?

Support Java Blogs By Hemant by becoming a sponsor. Any amount is appreciated!