The Singleton pattern controls the creation of a class so that one instance is available through a known access point. The original Gang of Four intent is concise: ensure that a class has one instance and provide global access to it. The difficult engineering work begins after that sentence. A design must define what “one” means, who owns the lifecycle, how concurrent callers are handled, and whether global access is actually desirable.
Singleton is a creational pattern because it changes how an object is obtained. Clients do not call a public constructor. The class, runtime, or dependency container controls creation and returns the designated instance.
A Java static field normally provides one value per loaded class, not one value for an entire distributed system. That distinction matters:
Singleton is therefore not a distributed locking mechanism. It is a local object-creation policy whose exact scope must be documented.
A typical implementation has three elements:
In modern Java, the initialization-on-demand holder idiom provides lazy initialization without writing explicit locking code:
public final class SimulationClock {
private SimulationClock() {
}
private static final class Holder {
private static final SimulationClock INSTANCE =
new SimulationClock();
}
public static SimulationClock getInstance() {
return Holder.INSTANCE;
}
}
Class initialization is synchronized by the Java Virtual Machine. The instance is created when Holder is first used, and all callers receive the same reference. This solves safe publication for the instance. It does not automatically make mutable operations inside SimulationClock thread-safe.
Singleton is defensible when the uniqueness requirement belongs to the domain or platform and the lifecycle truly spans the application. Possible examples include:
Even these examples require scrutiny. A logging facade may be globally reachable, while its appenders and destinations are managed as normal dependencies. Database applications generally use a connection pool, not one database connection shared by every request. Configuration can often be loaded once and passed as an immutable value.
A call such as SimulationClock.getInstance() is convenient, but it hides a dependency from constructors and method signatures. That can create several problems:
The private constructor is not the main risk. The larger risk is uncontrolled global access to mutable behavior.
| Requirement | Candidate design | Reason to prefer it |
|---|---|---|
| One service instance per application | Dependency-injection container scope | Central lifecycle with explicit client dependencies |
| Immutable shared values | Construct once and pass the value | No hidden mutable state |
| Many reusable expensive resources | Object pool | Bounded reuse rather than a single bottleneck |
| One owner across servers | External lease or leader election | Coordinates beyond one process |
| Stateless utility behavior | Static function or normal service | No artificial instance lifecycle |
This module examines Singleton as a design decision rather than a code recipe. The lessons progress through:
The traffic-signal simulation needs a consistent notion of simulated time. A process-local SimulationClock gives the module a concrete uniqueness requirement to evaluate. Later lessons ask whether static global access is necessary, how timing logic can be tested without waiting on real time, and how the design would change if several simulations ran in the same process.
Keep one principle in view throughout the module: prove the required uniqueness boundary before choosing Singleton. If ordinary ownership or dependency injection expresses the requirement more clearly, use the simpler design.
For a pattern-family reference, see the GOFPattern Singleton guide.