Thread Safe Singleton in Java: All Approaches Explained
Have you ever faced a situation where multiple objects of a class caused unexpected bugs in your application? That’s exactly where the…
Thread Safe Singleton in Java: All Approaches Explained
Have you ever faced a situation where multiple objects of a class caused unexpected bugs in your application? That’s exactly where the Singleton Design Pattern becomes useful. But basic Singleton is NOT thread safe and in real world applications, that can lead to serious issues.
In this article, we’ll explore:
- What Singleton is
- Why thread safety matters
- All approaches to make Singleton thread safe (with Java examples)
- Best practices used in the industry
What is Singleton Pattern?
The Singleton Pattern ensures that:
- Only one instance of a class exists
- A global access point is provided
Think of it like:
- A logger (only one needed)
- A configuration manager
- A database connection manager
Basic Singleton (NOT Thread-Safe)

Problem: In a multi-threaded environment, two threads can enter getInstance() at the same time:
Result:
- Multiple objects created
- Breaks Singleton principle
Why Thread Safety Matters
In real applications:
- Web servers handle multiple requests (multi threaded)
- Microservices run concurrent operations
If Singleton is not thread safe:
- Race conditions occur
- Data inconsistency happens
- Hard to debug production issues arise
Approach 1 : Eager Initalization

Here Instance is created during class loading and JVM class loader guarantees thread safety.
Benefit: Thread-safe by default
Drawback: Object created even if not used
Approach 2 : Synchronized Method

Here synchronized ensures no two threads create instance at same time and it prevents race condition.
Drawback: Since all calls to getInstance() is synchronized even after object is created. This is bad as locking is expensive and also it slows down application.
Approach 3 : Double checked locking method (DCL)
Creating a Singleton naively (just if (instance == null) { instance = new Singleton(); }) breaks in multithreaded environments because two threads can both pass the null check before either has finished creating the object. The fix is synchronized, but locking on every call to getInstance() is expensive. DCL is the optimization: lock only when the instance hasn't been created yet.

The operation instance = new Singleton() is actually three steps internally,
- Allocate raw memory for the object
- Initialize the object
- Assign the memory address to the
instancereference
Without volatile, a thread could see a non-null instance reference after step 1 but before step 3 which means it gets a pointer to a half-constructed, uninitialized object and tries to use it.
volatile forces the write to be immediately visible to all threads, and prevents this reordering.
The 1st check (instance == null) is for performance where it skips the lock if instance already exists.
The 2nd check (instance == null) acts as a correctness guard. If two threads pass the first check simultaneously, once one thread creates the instance, the second check prevents the other thread from creating another instance.
Benefits: High performance, Thread-safe
Drawback: Complex
Approach 4: Bill Pugh Singleton

Holderclass holds the instance and not loaded when Singleton is loaded. It only gets loaded the first time someone calls getInstance(). At that moment, the JVM initializes Holder, which creates the Singleton instance exactly once. Every call after that just returns the same object.
Why it’s thread-safe:
The JVM specification guarantees that class initialization is atomic. No two threads can initialize the same class simultaneously. So you get thread safety for free without writing a single synchronized or volatile keyword.
Why it’s better than Double-Checked Locking:
Double-checked locking achieves the same result, but it requires volatile and synchronized to be used correctly, which makes it complex. Missing even one keyword can introduce bugs. The Holder pattern does not require such keywords.
Benefits :
- Lazy loading
- Thread-safe
- No synchronization overhead
Approach 5: Enum Singleton

Enum constants are created when the enum class is loaded, and Java ensures that happens exactly once. They are implicitly public static finalwhere created once visible everywhere and never reassignable. You cannot call new Singleton() from anywhere because enums have no accessible constructor.
Benefits:
- Thread-safe by default
- Prevents: Reflection attacks , Serialization issues
Drawback : Less flexible
Comparison Table

Use Bill Pugh by default -lazy, fast, thread safe, no extra keywords needed. Use Enum if you need protection against serialization or reflection attacks. Avoid DCL unless you have a specific performance reason -one missing volatile and you have a production bug that's nearly impossible to reproduce. Never use Basic Singleton in any multithreaded environment.
When Should You Use Singleton?
Use when:
- You need shared resources
- Only one instance should control behavior
Examples:
- Logging systems
- Cache managers
- Configuration settings
When NOT to Use Singleton
Avoid when:
- It makes testing difficult
- Causes tight coupling
- You need multiple instances later
메타데이터
- post_id
- ea5b33841fe4
- slug
- thread-safe-singleton-in-java-all-approaches-explained-ea5b33841fe4
- url
- https://medium.com/@risni.jeeva/thread-safe-singleton-in-java-all-approaches-explained-ea5b33841fe4
- canonical_url
- https://medium.com/@risni.jeeva/thread-safe-singleton-in-java-all-approaches-explained-ea5b33841fe4
- author_url
- https://medium.com/@risni.jeeva
- status
- ok
- fetched_at
- 2026-06-27 10:07:59