Singleton Design Pattern in Java: Part 1
Deep dive into the Singleton design pattern in Java. Learn different implementation approaches including eager initialization and lazy loading.
Moshiour Rahman
Advertisement
What is the Singleton Pattern?
According to Wikipedia:
In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one “single” instance.
In simple terms, we use the Singleton pattern when we want only one instance of a class to exist throughout the application. It’s part of the Creational design patterns, as it deals with object creation.
Real-World Use Cases
Spring Framework
The Spring Framework uses the Singleton pattern extensively. Singleton is the default bean scope in the Spring container, meaning only one instance of each bean class is created per container.
Java Runtime
java.lang.Runtime is a classic example. Every Java application has a single instance of the Runtime class, accessed via getRuntime():
Runtime runtime = Runtime.getRuntime();
System.out.println("Available processors: " + runtime.availableProcessors());
System.out.println("Free memory: " + runtime.freeMemory());
Implementation Approaches
There are several ways to implement Singleton in Java:
- Declare a private constructor to prevent external instantiation
- Create the instance via a static field, static block, or static method
Important: Implementing thread-safe singletons is tricky in concurrent environments.
1. Eager Initialization
The simplest approach - create the instance at class loading time:
public class EagerSingleton {
// Instance created at class loading
private static final EagerSingleton INSTANCE = new EagerSingleton();
// Private constructor prevents instantiation
private EagerSingleton() {}
// Public access point
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
Pros:
- Simple and thread-safe (JVM handles class loading synchronization)
- No synchronization overhead
Cons:
- Instance is created even if never used
- Cannot handle exceptions during construction
- May impact startup time if initialization is expensive
2. Static Block Initialization
Similar to eager initialization, but allows exception handling:
public class StaticBlockSingleton {
private static final StaticBlockSingleton INSTANCE;
// Static block for complex initialization
static {
try {
// Add validation or configuration logic here
INSTANCE = new StaticBlockSingleton();
} catch (Exception e) {
throw new RuntimeException("Failed to create singleton", e);
}
}
private StaticBlockSingleton() {}
public static StaticBlockSingleton getInstance() {
return INSTANCE;
}
}
Pros:
- Exception handling during initialization
- Still thread-safe via class loading
Cons:
- Instance still created at class loading time
3. Lazy Initialization (Not Thread-Safe)
Create the instance only when first requested:
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
Problem: This is NOT thread-safe!
If two threads call getInstance() simultaneously when instance is null, both might create separate instances.
// Thread 1: checks instance == null (true)
// Thread 2: checks instance == null (true)
// Thread 1: creates new instance
// Thread 2: creates another new instance <- BUG!
4. Synchronized Lazy Initialization
Add synchronized to ensure thread safety:
public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
How it works:
The synchronized keyword ensures only one thread can execute getInstance() at a time, preventing race conditions.
Pros:
- Thread-safe
- Lazy initialization
Cons:
- Performance impact: Every call to
getInstance()requires synchronization - Reads cannot happen in parallel
- Once initialized, synchronization is unnecessary overhead
Performance Problem
With the synchronized approach:
// Even for simple reads after initialization:
SynchronizedSingleton.getInstance().doSomething(); // Acquires lock
SynchronizedSingleton.getInstance().doSomething(); // Acquires lock again
This synchronization overhead occurs on every call, even though it’s only needed during the first initialization.
Coming in Part 2
In the next article, we’ll explore solutions to this performance problem:
- Double-Checked Locking - Check before and after synchronization
- Bill Pugh Singleton - Using static inner class
- Enum Singleton - The recommended approach by Joshua Bloch
We’ll also cover:
- How to prevent singleton breaking via reflection
- How to handle serialization/deserialization
- Thread-safe lazy initialization without synchronization overhead
Summary
| Approach | Thread-Safe | Lazy | Performance |
|---|---|---|---|
| Eager | Yes | No | Good |
| Static Block | Yes | No | Good |
| Lazy (no sync) | No | Yes | Good |
| Synchronized | Yes | Yes | Poor |
Key Takeaways:
- Use eager initialization for simple cases
- Avoid basic lazy initialization in multi-threaded environments
- Synchronized method has significant performance overhead
- Better solutions exist (covered in Part 2)
Stay tuned for Part 2 where we’ll explore more efficient thread-safe approaches!
Advertisement
Moshiour Rahman
Software Architect & AI Engineer
Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.
Related Articles
Singleton Design Pattern in Java: Part 2
Advanced singleton implementations including Double-Checked Locking with volatile and the Bill Pugh pattern using static inner classes.
JavaUnderstanding Final Classes in Java
Learn when and why to use final classes in Java. Understand the design principle of prohibiting inheritance for better code safety.
JavaLombok: Reduce Java Boilerplate Code
Complete guide to Project Lombok for Java. Learn how to eliminate boilerplate code with annotations like @Data, @Builder, and @RequiredArgsConstructor.
Comments
Comments are powered by GitHub Discussions.
Configure Giscus at giscus.app to enable comments.