Singleton Pattern in Java (with Thread Safety)
The Singleton pattern explained: private constructor + static getInstance, the thread-safety trap, and the fixes (eager init, double-checked locking, enum). With Java code, UML, and when not to use it.
🟢 Design Patterns · Creational · Fresher → 🔴 the thread-safety gotcha
The Singleton is the most famous — and most misused — design pattern. Its job is simple: guarantee that a class has exactly one instance and give everyone a single global way to reach it. Think of a logger, a configuration holder, or a connection pool — things you want one of, shared everywhere. Easy to state, but the thread-safe version has a classic trap that interviewers love. Let's get it right.
The problem it solves
Some things should exist only once: a single app-wide configuration, one logger writing to one file, one pool of database connections. If code could freely new these up, you'd get inconsistent state, wasted resources, or conflicting writes. Singleton enforces "there can be only one."
The core idea
🟠Three moves make a Singleton:
- Make the constructor private so no one else can create an instance.
- Hold the single instance in a static field inside the class.
- Expose a static
getInstance()method that returns that one instance.
public class Logger {
private static Logger instance; // the one instance
private Logger() { } // private → no external "new"
public static Logger getInstance() {
if (instance == null) { // create it the first time (lazy)
instance = new Logger();
}
return instance; // always the same object
}
}
Now Logger.getInstance() anywhere returns the same object. This is lazy initialization — the instance is created only on first use.
The gotcha: thread safety
🔴 The code above is broken under concurrency. If two threads call getInstance() at the same time when instance is still null, both can pass the if check and each create an instance — now you have two Singletons. Interviewers ask this on purpose. The clean fixes:
- Eager initialization: create the instance at class-load time (
private static final Logger instance = new Logger();). Simple and thread-safe, but built even if never used. - Double-checked locking: guard creation with a lock and a
volatilefield, checking null twice to avoid locking on every call. - The best Java way — an enum:
enum Singleton { INSTANCE; }is thread-safe, lazy-ish, and even safe against serialization/reflection attacks. Naming the enum approach is a strong senior signal.
// Double-checked locking (thread-safe, lazy)
private static volatile Logger instance;
public static Logger getInstance() {
if (instance == null) { // 1st check (no lock)
synchronized (Logger.class) {
if (instance == null) // 2nd check (with lock)
instance = new Logger();
}
}
return instance;
}
When to use it — and when not to
🟠Use it for genuinely single, shared resources: logging, configuration, a connection/thread pool, a cache. Be cautious: Singletons are essentially global state, which makes code harder to test (you can't easily swap the instance for a mock) and can hide dependencies. Many teams prefer dependency injection — creating one instance and injecting it — which gives you "one instance" without the global-access downsides. So know the pattern, but don't reach for it reflexively.
The interview-ready summary
"Singleton guarantees one instance via a private constructor + a static getInstance(). The classic trap is thread safety: naive lazy init lets two threads create two instances, so I'd use eager init, double-checked locking with volatile, or best, an enum Singleton. I'd also flag that it's global state — harder to test — so dependency injection is often a cleaner way to get a single shared instance." That shows you know both the pattern and its pitfalls."
What to read next
- Factory Method Pattern — the next creational pattern
- All Design Patterns (hub) — the full map
- SOLID Principles — why DI often beats Singleton
- ← The complete System Design guide (hub)