Skip to main content

Command Palette

Search for a command to run...

Singleton Pattern in Java – Explained with Examples

Published
7 min readView as Markdown
Singleton Pattern in Java – Explained with Examples

Introduction

In software development, there are often situations where it is required to have a single instance of an object or feature that can serve as a global access point for the entire application. Creating more than one instance might result in inaccurate program behavior or unnecessary overhead, which can lead to application performance degradation.

This is where the Singleton Design Pattern comes into the picture. It is one of the simplest design patterns used to control the creation process, ensuring that only one instance of a class ever exists and providing a way to access that instance across the application.


What is the Singleton Pattern?

Singleton Pattern is a creational design pattern that guarantees a class has only one instance and provides a global point of access to it.

It involves only one class, which is responsible for instantiating itself and ensuring that only one instance is created.

To implement the Singleton pattern, we must prevent external objects from creating instances of the Singleton class. Only the Singleton class should be able to instantiate itself.

Additionally, we need to ensure we provide a global access method for external objects to access the singleton instance.


Class Diagram

In Java, one of the ways to implement the Singleton pattern is:

  • by making the constructor private

  • and providing a static method for external access


Implementation

There are several ways to implement the Singleton pattern depending on requirements.


1. Eager Initialization

In this approach, the responsibility of instance creation lies with the JVM and happens at class loading time.

class EagerSingleton {
    // instance initialized at the time of class loading
    private static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton() {
        // Private constructor to prevent instantiation
        System.out.println("EagerSingleton instance created.");
    }

    public static EagerSingleton getInstance() {
        System.out.println("getInstance called.");
        return instance;
    }
}

public class EagerSingletonExample {
    static void main() {
        EagerSingleton object1 = EagerSingleton.getInstance();
        EagerSingleton object2 = EagerSingleton.getInstance();

        System.out.println("Are both instances the same? " + (object1 == object2));

    }
}

Explanation

As we can see in the output, the instance is created once when the program runs, and any external objects requiring the instance can use the same instance accessible through a public static method.

Program Output

EagerSingleton instance created.
getInstance called.
getInstance called.
Are both instances the same? true

Pros

  • Easy to implement

  • Inherently thread-safe

Cons

  • Can result in performance overhead if the instance is never used

2. Lazy Initialization

In this approach, the instance gets created only when it is required for the first time.

class LazySingleton {
    // instance gets created only when it is requested for the first time
    private static LazySingleton instance;

    private LazySingleton() {
        // Private constructor to prevent instantiation
        System.out.println("LazySingleton instance created.");
    }

    public static LazySingleton getInstance() {
        System.out.println("getInstance called.");
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

public class LazySingletonExample {
    static void main() {
        LazySingleton object1 = LazySingleton.getInstance();
        LazySingleton object2 = LazySingleton.getInstance();

        System.out.println("Are both instances the same? " + (object1 == object2));
    }
}

Program Output

getInstance called.
LazySingleton instance created.
getInstance called.
Are both instances the same? true

Pros

  • Saves resources by creating the instance only when required

Cons

  • Not thread-safe


3. Thread-Safe Singleton

We can make the Lazy Singleton implementation thread-safe by synchronizing the static getInstance() method.

class ThreadSafeSingleton {
    // instance creation similar to Lazy initialization but with thread safety
    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton() {
        // Private constructor to prevent instantiation
        System.out.println("ThreadSafeSingleton instance created.");
    }

    public static synchronized ThreadSafeSingleton getInstance() {
        System.out.println("getInstance called.");
        if (instance == null) {
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }
}

public class ThreadSafeSingletonExample {
    static void main() {
        ThreadSafeSingleton object1 = ThreadSafeSingleton.getInstance();
        ThreadSafeSingleton object2 = ThreadSafeSingleton.getInstance();

        System.out.println("Are both instances the same? " + (object1 == object2));
    }
}

Program Output

getInstance called.
ThreadSafeSingleton instance created.
getInstance called.
Are both instances the same? true

Pros

  • Simple and thread-safe

Cons

  • Performance overhead due to synchronization

4. Double-Checked Locking

This approach reduces synchronization overhead.

class DoubleCheckedSingleton {
    // instance with double-checked locking for thread safety and performance
    private static volatile DoubleCheckedSingleton instance;

    private DoubleCheckedSingleton() {
        // Private constructor to prevent instantiation
        System.out.println("DoubleCheckedSingleton instance created.");
    }

    public static DoubleCheckedSingleton getInstance() {
        System.out.println("getInstance called.");
        if (instance == null) {
            synchronized (DoubleCheckedSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedSingleton();
                }
            }
        }
        return instance;
    }
}

public class DoubleCheckedSingletonExample {
    static void main() {
        DoubleCheckedSingleton object1 = DoubleCheckedSingleton.getInstance();
        DoubleCheckedSingleton object2 = DoubleCheckedSingleton.getInstance();

        System.out.println("Are both instances the same? " + (object1 == object2));
    }
}

Program Output

getInstance called.
DoubleCheckedSingleton instance created.
getInstance called.
Are both instances the same? true

Pros

  • Better performance

  • Thread-safe

Cons

  • Slightly complex implementation


5. Bill Pugh Singleton Implementation

This approach uses a static inner helper class.

class BillPughSingleton {
    // Lazy and thread-safe singleton implementation using Bill Pugh's method
    private BillPughSingleton() {
        // Private constructor to prevent instantiation
        System.out.println("BillPughSingleton instance created.");
    }

    // Inner static helper class responsible for holding the singleton instance
    private static class SingletonHelper {
        private static final BillPughSingleton instance = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance() {
        System.out.println("getInstance called.");
        return SingletonHelper.instance;
    }
}

public class BillPughSingletonExample {
    static void main() {
        BillPughSingleton object1 = BillPughSingleton.getInstance();
        BillPughSingleton object2 = BillPughSingleton.getInstance();

        System.out.println("Are both instances the same? " + (object1 == object2));
    }
}

Program Output

getInstance called.
BillPughSingleton instance created.
getInstance called.
Are both instances the same? true

Pros

  • Lazy loading

  • Thread-safe

  • No synchronization overhead

Cons

  • Slightly harder to understand for beginners

6. Static Block Initialization

Similar to eager initialization but allows exception handling.

class StaticBlockSingleton {
    // instance created in static block for improved exception handling
    private static StaticBlockSingleton instance;

    private StaticBlockSingleton() {
        // Private constructor to prevent instantiation
        System.out.println("StaticBlockSingleton instance created.");
    }

    // Static block for instance creation
    static {
        try {
            instance = new StaticBlockSingleton();
        } catch (Exception exception) {
            throw new RuntimeException("Exception occurred in creating singleton instance", exception);
        }
    }

    public static StaticBlockSingleton getInstance() {
        System.out.println("getInstance called.");
        return instance;
    }
}

public class StaticBlockSingletonExample {
    static void main() {
        StaticBlockSingleton object1 = StaticBlockSingleton.getInstance();
        StaticBlockSingleton object2 = StaticBlockSingleton.getInstance();

        System.out.println("Are both instances the same? " + (object1 == object2));
    }
}

Program Output

StaticBlockSingleton instance created.
getInstance called.
getInstance called.
Are both instances the same? true

Pros

  • Eager based straightforward implementation.

  • It is thread safe and also provides exception handling.

Cons

  • It can have slight performance overhead as it is eager based loading specially if the object creation is resource-intensive or time-consuming.

7. Enum Singleton

One of the cleanest and safest implementations.

enum Singleton {
    // Single instance created when the enum is loaded
    INSTANCE;

    Singleton() {
        System.out.println("Enum Singleton instance created.");
    }

    public void someMethod() {
        System.out.println("someMethod called.");
    }
}

public class EnumSingletonExample {
    static void main() {
        Singleton object1 = Singleton.INSTANCE;
        object1.someMethod();

        Singleton object2 = Singleton.INSTANCE;
        System.out.println("Are both instances the same? " + (object1 == object2));

    }
}

Program Output

Enum Singleton instance created.
someMethod called.
Are both instances the same? true

Pros

  • Thread-safe

  • Reflection-safe

  • Serialization-safe

Cons

  • Not suitable when lazy initialization is required

Real World Use Cases of Singleton Pattern

  • Managing shared resources (database connections, thread pools, caches)

  • Logging services

  • Configuration settings

  • Managing application state


When NOT to Use Singleton

  • When global state can lead to tight coupling

  • When unit testing becomes difficult

  • When dependency injection is preferred


Conclusion

In this article, we briefly learned about the Singleton pattern, what it is, and the various implementation approaches available based on use cases. We also looked at the pros and cons of each approach.

The Singleton pattern is fundamental and useful, but it should be used judiciously as it introduces global state management and can make testing and maintenance more challenging. Consider approaches like dependency injection when possible to promote loose coupling and better testability.