Design Patterns - Singleton (in Java)

Design Patterns - Singleton (in Java)

The ultimate guide on singleton design pattern

Definition

A class should have only one instance and that should be accessible globally.

This is a Creational Design Pattern.

Idea Behind Singleton Design Pattern

  • Limit object creation by making the constructor private

  • Expose a public static method to get the instance of that class

Implementation

There are multiple ways to implement singleton design patterns.

Eager Initialization

The main idea is to create an object and make it final. Always return that single object from the static method.

public class Singleton {
    private static final Singleton instance = new Singleton(); // egar initialization

    private Singleton() { // private constructor to prohibit object creation
    }

    public static Singleton getInstance() { // public static method to expose already created single instance
        return instance;
    }
}

Lazy Initialization

The idea is to create a single instance only when a client requests it and always return the same instance each time the client asks. This is a memory-optimized implementation.

public class Singleton {
    private static Singleton instance;

    private Singleton() { // private constructor to prohibit object creation
    }

    public static Singleton getInstance() { // public static method to expose single instance
        if (instance == null) { // lazy initialization:
            // create single instance when client ask for it for the first time
            instance = new Singleton();
        }
        return instance;
    }
}

Limitations

This implementation is correct as long as you are thinking in a single-threaded way.

There is a high chance of calling getInstance() from multiple threads create multiple instances of the Singleton class. So this implementation is not thread-safe.

Thread-Safe Singleton

Use synchronized keyword on getInstance() method to ensure thread safety.

public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

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

Or we can use synchronized block inside getInstance()method.

public class Singleton {
    private static Singleton instance;

    private Singleton() {
    }

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

But the use of synchronized is a costly operation as it involves locking and unlocking the code chunk.

So a tricky solution could be use synchronized conditionally.

Double-Checked Locking

public class Singleton {
    private static volatile Singleton instance; // use volatile to ensure visibility
    // and solve instruction re-ordering problem

    private Singleton() {
    }

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

Bill Pugh Singleton Design

This is named after the inventor of this implementation Bill Pugh.

public class Singleton {
    private Singleton() {
    }

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

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

This nice tricky implementation is also known as the Initialization-on-demand holder idiom.

This ensures lazy initialization as well as thread safety. It is less complex to implement. As there is no synchronization and locking - it reduces performance overhead.

Importance of Singleton Design Pattern

  • For some resources e.g. database connection, logging, configurations, etc. we want only one instance so that it reduces duplication, and ensures correctness.

  • In some cases, object creation may be costly and unnecessary, in those cases, singleton is the escape.

  • There might be a situation when you need control over instances of a class, singleton is very useful in that scenario.

  • Some practical examples of singleton are - transaction management, global thread pool manager, etc.