This module treated Singleton as a design decision, not merely as a class with a private constructor. The pattern controls creation so that one instance is available through a known access point. A sound application must also define the uniqueness boundary, lifecycle, concurrency policy, and cost of global access.
The central lesson is conditional: Singleton can be appropriate when one process-local owner is a real requirement, but it is harmful when it disguises ordinary dependencies as global state.
The lessons used the standard pattern elements to move from a vague desire for “one object” to a reviewable design:
This vocabulary makes the design explainable. It also prevents an implementation technique from being mistaken for a complete architectural justification.
A conventional Java Singleton is generally one instance per loaded class. Separate class loaders or application processes can each create their own instance. A static field cannot enforce one owner across a cluster.
Before selecting the pattern, state the boundary explicitly:
The last requirement needs external coordination such as a lease, database constraint, or leader-election service. Singleton alone cannot provide it.
| Java approach | Useful property | Primary caution |
|---|---|---|
| Eager static field | Simple safe publication through class initialization | Constructs the object when the class initializes |
| Synchronized accessor | Clear lazy initialization | Acquires the class monitor on each call |
| Initialization-on-demand holder | Lazy initialization using JVM guarantees | Still exposes a global dependency |
| Enum | Concise construction and serialization behavior | Enum-shaped API may not fit every design |
| Double-checked locking | Avoids locking after initialization | Requires volatile and more difficult reasoning |
Thread-safe creation does not make the Singleton's mutable fields thread-safe. Compound domain operations need their own locking, immutability, atomicity, or single-threaded processing policy.
The module compared Singleton with designs that often express the real requirement more clearly:
These are not automatic replacements. They are candidate designs to compare against the same forces and consequences.
The traffic-signal project uses a shared simulation clock to make the uniqueness discussion concrete. One clock can give every signal a consistent timeline within one simulation process. The design must still answer several questions:
If multiple simulations are required, an injected clock owned by each simulation is a better model than one process-wide Singleton. This illustrates why requirements, not pattern enthusiasm, should determine the scope.
Lesson 13 compared eager initialization, synchronized access, the holder idiom, enum construction, and double-checked locking. Each can be technically correct under stated conditions. None eliminates Singleton's architectural costs.
The word “perfect” is therefore replaced by a more useful standard: the implementation is robust when its uniqueness boundary is justified, publication is safe, mutable state has a concurrency policy, lifecycle behavior is explicit, and clients can be tested without uncontrolled shared state.
If these questions do not have precise answers, delay the pattern decision and keep the dependency explicit.
The next module broadens the discussion from Singleton to other creational patterns and the different ways they separate clients from concrete construction decisions.