Core Concept
A Singleton guarentees that a given class produces exactly one object during the application’s lifetime and exposes that object through a well-known, globally reachable accessor. The pattern is invaluable when a single logical entity—such as configuration data, a connection dispatcher, or an in-memory cache—must remain unique and consistent.
Canonical Structure
- Hidden constructor – prevents external instantiation.
- Static storage slot – holds the sole reference.
- Global accessor – returns the stored reference, creating it lazily if necessary.
Eager Initialization
The simplest form pre-creates the instance when the class is loaded:
public final class AppConfig {
private static final AppConfig soleInstance = new AppConfig();
private AppConfig() {}
public static AppConfig instance() { return soleInstance; }
}
Pros: trivial, inherently thread-safe.
Cons: instance is always built, even if never used.
Lazy Initialization with Synchronization
Postponing construction until first use saves resources but introduces race conditions:
public final class ConnectionPool {
private static ConnectionPool pool;
private ConnectionPool() {}
public static synchronized ConnectionPool getPool() {
if (pool == null) {
pool = new ConnectionPool();
}
return pool;
}
}
The synchronized keyword ensures correctness, yet every access pays the locking cost.
Double-Checked Locking with volatile
Reduce synchronization overhead by guarding the critical section:
public final class LogManager {
private static volatile LogManager core;
private LogManager() {}
public static LogManager core() {
LogManager result = core; // local copy boosts performance on some JVMs
if (result == null) {
synchronized (LogManager.class) {
result = core;
if (result == null) {
core = result = new LogManager();
}
}
}
return result;
}
}
The volatile keyword prevents instruction re-ordering that could expose a half-built object.
Initialization-on-Demand Holder
Leverages the JVM’s class-loader semantics for a lock-free, lazy solution:
public final class CacheHolder {
private CacheHolder() {}
private static class Holder {
private static final CacheHolder INSTANCE = new CacheHolder();
}
public static CacheHolder getInstance() { return Holder.INSTANCE; }
}
The nested Holder class is not loaded until referenced, making the instance creation both lazy and thread-safe without explicit synchronization.
Enum Singleton
Java enums provide an iron-clad, serialization-proof singleton:
public enum Registry {
INSTANCE;
// fields and methods
}
Usage: Registry.INSTANCE.doWork(). The enum approach is concise and immune to reflection and serialization attacks.
Typical Use Cases
- Central configuration repository
- JDBC connection pool
- Application-wide event bus
- File-system metadata cache
- Cross-cutting logger
Advantages
- Single shared state eliminates duplication.
- Controlled access point simplifies debugging.
Reduced memory footprint when the resource is heavyweight.
Drawbacks
- Global state complicates unit testing and parallel execution.
- Violates Single Responsibility Principle by mixing lifecycle control with bussiness logic.
- Can mask design flaws where dependency injection would be cleaner.
Modern Alternatives
- Dependency Injection Containers – frameworks such as Spring or Guice manage object lifecycles and scoping transparently.
- Static Utility Classes – suitable when no mutable state is required.
- Scoped Factories – create short-lived, injectable objects instead of a single global one.
Quick Checklist
- Is the resource truly unique by nature?
- Will concurrent access be heavy enough to justify double-checked locking?
- Can the design be expressed with DI instead of a singleton?
- Does the choice simplify or complicate future testing?
Careful answers to these questions determine weather a Singleton is the right tool tool or a shortcut that will technical debt.