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
Post a Comment