How To Implement The Singleton Design Pattern

This recipe shows how to implement the Singleton design pattern. This design pattern can be used when you need to restrict the instantiation of a class to a single instance, such as when implementing logging, caching, thread pooling, configuration, etc.

The Singleton design pattern usually consists of:

We will explore four different approaches to implementing this design pattern.

Each approach comes with its own set of pros and cons. Your choice of approach will depend upon your specific requirements and use cases; however, the preferred approach in most cases is option 4.

Option 1: Eager Initialization

The first example shows how to implement the Singleton design pattern using eager initialization with a public static factory method.

Pros:

Cons:

public class SingletonEager implements Serializable {

    private static final long serialVersionUID = 1L;

    // eager initialization
    private static SingletonEager instance = new SingletonEager();

    // hide the constructor
    private SingletonEager() {
    }

    // factory method to access single instance
    public static SingletonEager getInstance() {
        return instance;
    }

    // required in order to preserve singleton property for "serde"
    private Object readResolve() {
        return getInstance();
    }

    // other methods...
    public String doSomething() {
        return "do something";
    }

    public static void main(String[] args) throws Exception {
        final SingletonEager instance1 = SingletonEager.getInstance();
        final SingletonEager instance2 = SingletonEager.getInstance();
        if (instance1 == instance2) {
            System.out.println("instance1 and instance2 are the same instance");
        } else {
            System.out.println("instance1 and instance2 are different instances");
        }

        // verify that "serde" works properly
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        final ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(instance1);
        oos.close();
        final InputStream is = new ByteArrayInputStream(baos.toByteArray());
        final ObjectInputStream ois = new ObjectInputStream(is);
        final SingletonEager instance3 = (SingletonEager) ois.readObject();
        if (instance1 == instance3) {
            System.out.println("instance1 and instance3 are the same instance");
        } else {
            System.out.println("instance1 and instance3 are different instances");
        }
        ois.close();
    }
}

Link To: Java Source Code

Option 1 Output:

instance1 and instance2 are the same instance
instance1 and instance3 are the same instance

Option 2: Lazy Initialization

The second example shows how to implement the Singleton design pattern using lazy initialization with a public static factory method. It is designed for use in a single-threaded environment only.

Pros:

Cons:

public class SingletonLazy implements Serializable {

    private static final long serialVersionUID = 1L;
    private static SingletonLazy instance = null;

    // hide the constructor
    private SingletonLazy() {
    }

    // factory method to access single instance
    public static SingletonLazy getInstance() {
        // lazy initialization (not thread safe)
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }

    // required in order to preserve singleton property for "serde"
    private Object readResolve() {
        return getInstance();
    }

    // other methods...
    public String doSomething() {
        return "do something";
    }

    public static void main(String[] args) throws Exception {
        final SingletonLazy instance1 = SingletonLazy.getInstance();
        final SingletonLazy instance2 = SingletonLazy.getInstance();
        if (instance1 == instance2) {
            System.out.println("instance1 and instance2 are the same instance");
        } else {
            System.out.println("instance1 and instance2 are different instances");
        }

        // verify that "serde" works properly
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        final ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(instance1);
        oos.close();
        final InputStream is = new ByteArrayInputStream(baos.toByteArray());
        final ObjectInputStream ois = new ObjectInputStream(is);
        final SingletonLazy instance3 = (SingletonLazy) ois.readObject();
        if (instance1 == instance3) {
            System.out.println("instance1 and instance3 are the same instance");
        } else {
            System.out.println("instance1 and instance3 are different instances");
        }
        ois.close();
    }
}

Link To: Java Source Code

Option 2 Output:

instance1 and instance2 are the same instance
instance1 and instance3 are the same instance

Option 3: Initialization-On-Demand Holder

The third example shows how to implement the Singleton design pattern following the initialization-on-demand holder idiom with a public static factory method. It uses lazy initialization and is thread safe.

Pros:

Cons:

public class SingletonHolder implements Serializable {

    private static final long serialVersionUID = 1L;

    private static class LazyHolder {
        static final SingletonHolder INSTANCE = new SingletonHolder();
    }

    // hide the constructor
    private SingletonHolder() {
    }

    // factory method to access single instance
    public static SingletonHolder getInstance() {
        // lazy initialization
        return LazyHolder.INSTANCE;
    }

    // required in order to preserve singleton property for "serde"
    private Object readResolve() {
        return getInstance();
    }

    // other methods...
    public String doSomething() {
        return "do something";
    }

    public static void main(String[] args) throws Exception {
        final SingletonHolder instance1 = SingletonHolder.getInstance();
        final SingletonHolder instance2 = SingletonHolder.getInstance();
        if (instance1 == instance2) {
            System.out.println("instance1 and instance2 are the same instance");
        } else {
            System.out.println("instance1 and instance2 are different instances");
        }

        // verify that "serde" works properly
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        final ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(instance1);
        oos.close();
        final InputStream is = new ByteArrayInputStream(baos.toByteArray());
        final ObjectInputStream ois = new ObjectInputStream(is);
        final SingletonHolder instance3 = (SingletonHolder) ois.readObject();
        if (instance1 == instance3) {
            System.out.println("instance1 and instance3 are the same instance");
        } else {
            System.out.println("instance1 and instance3 are different instances");
        }
        ois.close();
    }
}

Link To: Java Source Code

Option 3 Output:

instance1 and instance2 are the same instance
instance1 and instance3 are the same instance

Option 4: Single-Element Enum Type (preferred)

The final example shows how to implement the Singleton design pattern using a single-element Enum type, which provides a lazy initialization, thread-safe and serializable implementation.

Pros:

Cons:

public enum SingletonEnum {
    INSTANCE;

    // other methods...
    public String doSomething() {
        return "do something";
    }

    public static void main(String[] args) throws Exception {
        final SingletonEnum instance1 = SingletonEnum.INSTANCE;
        final SingletonEnum instance2 = SingletonEnum.INSTANCE;
        if (instance1 == instance2) {
            System.out.println("instance1 and instance2 are the same instance");
        } else {
            System.out.println("instance1 and instance2 are different instances");
        }

        // verify that "serde" works properly
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        final ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(instance1);
        oos.close();
        final InputStream is = new ByteArrayInputStream(baos.toByteArray());
        final ObjectInputStream ois = new ObjectInputStream(is);
        final SingletonEnum instance3 = (SingletonEnum) ois.readObject();
        if (instance1 == instance3) {
            System.out.println("instance1 and instance3 are the same instance");
        } else {
            System.out.println("instance1 and instance3 are different instances");
        }
        ois.close();
    }
}

Link To: Java Source Code

Option 4 Output:

instance1 and instance2 are the same instance
instance1 and instance3 are the same instance