Singleton Design Pattern in Java Explained with Examples | Beginner to Advanced Guide

Reading Time: 15–20 minutes

The Singleton Design Pattern is one of the most popular creational design patterns in Java. It ensures that only one instance of a class exists throughout the application and provides a global access point to that instance.

This pattern is widely used in enterprise applications for managing shared resources such as configuration objects, logging services, caching, thread pools, and database connections.

This guide explains Singleton from beginner to advanced level with simple explanations, real-world examples, thread safety concepts, performance optimization, and ways Singleton can be broken.

What is Singleton Design Pattern

The Singleton pattern ensures that a class has only one object and provides a global method to access that object.

In simple words, Singleton solves two problems:

Ensure only one object is created.
Provide global access to that object.

Instead of creating multiple objects using the new keyword, the application uses a single shared instance.

Why Do We Need Singleton

Some resources in an application should not have multiple instances because it may cause inconsistency, high memory usage, or incorrect behavior.

Common Use Cases

Configuration manager
Logger service
Cache manager
Thread pool
Database connection pool

Creating multiple instances of these objects can lead to performance problems or inconsistent data.

Key Rules to Create Singleton

A class must follow three rules to become a Singleton:

Constructor must be private so no external object can be created.
A static instance variable holds the single object.
A public static method provides global access.

Types of Singleton Implementation

Basic Singleton Implementation (Eager Initialization)

In eager initialization, the instance is created when the class is loaded into memory.


package com.codekatha;

class Singleton {

    private static final Singleton instance = new Singleton();

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

    public static Singleton getInstance() {
        return instance;
    }
}

This approach is simple and thread-safe because Java class loading is thread-safe. However, the object is created even if it is never used.

Lazy Initialization Singleton

Lazy initialization creates the object only when it is required.


package com.codekatha;

class LazySingleton {

    private static LazySingleton instance;

    private LazySingleton() {}

    public static LazySingleton getInstance() {

        if (instance == null) {
            instance = new LazySingleton();
        }

        return instance;
    }
}

This saves memory but is not thread-safe.

Thread Safety Problem in Lazy Singleton

Consider two threads calling getInstance() at the same time.

Thread A checks instance is null.
Thread B checks instance is null.
Both create objects.

This breaks the Singleton rule.

Thread-Safe Singleton Using Synchronized Method

We can make the method synchronized to allow only one thread at a time.


package com.codekatha;

class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton() {}

    public static synchronized ThreadSafeSingleton getInstance() {

        if (instance == null) {
            instance = new ThreadSafeSingleton();
        }

        return instance;
    }
}

This solves the thread safety problem but reduces performance because every call requires locking.

Double-Checked Locking Singleton

Double-Checked Locking improves performance by reducing unnecessary synchronization while maintaining thread safety.


package com.codekatha;

class DoubleCheckedSingleton {

    private static volatile DoubleCheckedSingleton instance;

    private DoubleCheckedSingleton() {}

    public static DoubleCheckedSingleton getInstance() {

        if (instance == null) {
            synchronized (DoubleCheckedSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedSingleton();
                }
            }
        }
        return instance;
    }
}

Understanding Double-Checked Locking

This approach checks the instance twice.

First check avoids locking when the object already exists.
Second check ensures only one thread creates the object.

Imagine students checking if a classroom teacher has arrived:

First check: Student looks inside room.
If teacher present → no action.
If teacher not present → lock door and check again.

This avoids unnecessary locking and ensures only one teacher is called.

Why Volatile is Required

The volatile keyword prevents instruction reordering and ensures all threads see the fully constructed object.

Object creation internally happens in three steps:

Allocate memory
Assign reference
Initialize object

Without volatile, another thread may see a partially created object.

Bill Pugh Singleton (Recommended Approach)

This implementation uses a static inner class and provides lazy loading with thread safety.


package com.codekatha;

class BillPughSingleton {

    private BillPughSingleton() {}

    private static class Helper {
        private static final BillPughSingleton INSTANCE =
                new BillPughSingleton();
    }

    public static BillPughSingleton getInstance() {
        return Helper.INSTANCE;
    }
}

Enum Singleton (Safest Implementation)

Java recommends using enum for Singleton because it prevents reflection and serialization attacks.


package com.codekatha;

enum EnumSingleton {

    INSTANCE;

    public void showMessage() {
        System.out.println("Singleton using Enum");
    }
}

How Singleton Can Be Broken

Even a correctly implemented Singleton can be broken if not protected properly.

Breaking Singleton Using Reflection

Reflection allows access to private constructors.


package com.codekatha;

import java.lang.reflect.Constructor;

class ReflectionBreak {

    public static void main(String[] args) throws Exception {

        Singleton s1 = Singleton.getInstance();

        Constructor<Singleton> constructor =
                Singleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);

        Singleton s2 = constructor.newInstance();

        System.out.println(s1 == s2);
    }
}

Reflection bypasses access control and creates another object.

Preventing Reflection Attack


package com.codekatha;

class SafeSingleton {

    private static SafeSingleton instance = new SafeSingleton();

    private SafeSingleton() {
        if (instance != null) {
            throw new RuntimeException("Use getInstance()");
        }
    }

    public static SafeSingleton getInstance() {
        return instance;
    }
}

Breaking Singleton Using Serialization

Serialization creates a new object during deserialization.


package com.codekatha;

import java.io.Serializable;

class SerializableSingleton implements Serializable {

    private static final SerializableSingleton instance =
            new SerializableSingleton();

    private SerializableSingleton() {}

    public static SerializableSingleton getInstance() {
        return instance;
    }

    protected Object readResolve() {
        return instance;
    }
}

The readResolve method ensures the same instance is returned.

Advantages of Singleton

Single shared instance
Memory efficient
Global access point
Controlled resource usage

Disadvantages of Singleton

Difficult to test due to global state
Can create tight coupling
May violate Single Responsibility Principle if overused

Real-World Usage

Logging frameworks
Configuration management
Cache systems
Spring beans (default singleton scope)
Thread pool management

Interview Questions on Singleton

Why is constructor private?
What is lazy vs eager initialization?
Why use volatile in Double-Checked Locking?
How can Singleton be broken?
Why Enum Singleton is recommended?

Conclusion

The Singleton Design Pattern ensures controlled object creation and shared resource management. While the concept is simple, implementing it correctly requires understanding thread safety, performance optimization, and security concerns.

For modern Java applications, Bill Pugh Singleton and Enum Singleton are generally recommended due to simplicity and reliability.

Comments

Popular Posts on Code Katha

Java Interview Questions for 10 Years Experience

Sql Interview Questions for 10 Years Experience

Spring Boot Interview Questions for 10 Years Experience

Java interview questions - Must to know concepts

Visual Studio Code setup for Java and Spring with GitHub Copilot

Spring AI with Ollama

Data Structures & Algorithms Tutorial with Coding Interview Questions

Spring Data JPA

Topological Sort in Graph

Bit Manipulation and Bit Masking Concepts