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;
}
}
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 is the process of converting an object into a byte stream so that it can be stored in a file or transferred over a network.
During deserialization, JVM creates a completely new object in memory instead of returning the existing instance.
This breaks the Singleton rule because multiple objects get created.
Why Serialization Breaks Singleton
Object state is saved to a file.
During deserialization JVM creates a new object.
Constructor is not called.
A new instance exists in memory.
This results in multiple instances of Singleton.
Example: Breaking Singleton Using Serialization
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;
}
}
Now serialize and deserialize the object.
package com.codekatha;
import java.io.*;
public class SerializationBreak {
public static void main(String[] args) throws Exception {
SerializableSingleton s1 =
SerializableSingleton.getInstance();
// Serialize
ObjectOutputStream out =
new ObjectOutputStream(
new FileOutputStream("file.ser"));
out.writeObject(s1);
out.close();
// Deserialize
ObjectInputStream in =
new ObjectInputStream(
new FileInputStream("file.ser"));
SerializableSingleton s2 =
(SerializableSingleton) in.readObject();
in.close();
System.out.println(s1 == s2);
}
}
Output: false
Two different objects are created. Singleton is broken.
Preventing Singleton Breaking from Serialization
We can prevent this using readResolve() method.
This method ensures JVM returns the existing instance instead of creating a new one.
Fixed Singleton Implementation
class SerializableSingleton implements Serializable {
private static final SerializableSingleton instance =
new SerializableSingleton();
private SerializableSingleton() {}
public static SerializableSingleton getInstance() {
return instance;
}
protected Object readResolve() {
return instance;
}
}
During deserialization:
JVM creates new object → readResolve() called → existing instance returned.
This guarantees Singleton property.
Breaking Singleton Using Cloning
If a Singleton class implements Cloneable interface, cloning can create a new object.
The clone() method produces a copy of an existing object, which breaks Singleton.
Example: Singleton with Cloneable
package com.codekatha;
class CloneableSingleton implements Cloneable {
private static final CloneableSingleton instance =
new CloneableSingleton();
private CloneableSingleton() {}
public static CloneableSingleton getInstance() {
return instance;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Breaking Code
package com.codekatha;
public class CloneBreak {
public static void main(String[] args) throws Exception {
CloneableSingleton s1 =
CloneableSingleton.getInstance();
CloneableSingleton s2 =
(CloneableSingleton) s1.clone();
System.out.println(s1 == s2);
}
}
Output: false
A new instance is created using clone(). Singleton is broken.
Preventing Singleton Breaking from Cloning
We can override clone() method and prevent object creation.
Safe Implementation
class CloneSafeSingleton implements Cloneable {
private static final CloneSafeSingleton instance =
new CloneSafeSingleton();
private CloneSafeSingleton() {}
public static CloneSafeSingleton getInstance() {
return instance;
}
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("Singleton cannot be cloned");
}
}
This prevents cloning completely.
Another approach is returning the same instance:
@Override
protected Object clone() {
return instance;
}
Best Protection: Enum Singleton
Java recommends Enum Singleton because it automatically protects against:
Reflection attacks
Serialization attacks
Cloning attacks
enum EnumSingleton {
INSTANCE;
public void showMessage() {
System.out.println("Singleton using Enum");
}
}
Enum Singleton is thread-safe, serialization-safe, and reflection-safe by design.
This is considered the safest and simplest Singleton implementation in modern Java.
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