Singleton Design Pattern
Context
In software engineering, certain classes represent concepts that should only exist once during the entire execution of a program. Common examples include thread pools, caches, dialog boxes, logging objects, and device drivers. In these scenarios, having more than one instance is not just unnecessary but often harmful to the system’s integrity. In a UML class diagram, this requirement is explicitly modeled by specifying a multiplicity of “1” in the upper right corner of the class box, indicating the class is intended to be a singleton.
Problem
The primary problem arises when instantiating more than one of these unique objects leads to incorrect program behavior, resource overuse, or inconsistent results. For instance, accidentally creating two distinct “Earth” objects in a planetary simulation would break the logic of the system.
While developers might be tempted to use global variables to manage these unique objects, this approach introduces several critical flaws:
- High Coupling: Global variables allow any part of the system to access and potentially mess around with the object, creating a web of dependencies that makes the code hard to maintain.
- Lack of Control: Global variables do not prevent a developer from accidentally calling the constructor multiple times to create a second, distinct instance.
- Instantiation Issues: You may want the flexibility to choose between “eager instantiation” (creating the object at program start) or “lazy instantiation” (creating it only when first requested), which simple global variables do not inherently support.
Solution
The Singleton Pattern solves these issues by ensuring a class has only one instance while providing a controlled, global point of access to it. The solution consists of three main implementation aspects:
- A Private Constructor: By declaring the constructor
private, the pattern prevents external classes from ever using thenewkeyword to create an instance. - A Static Field: The class maintains a private static variable (often named
uniqueInstance) to hold its own single instance. - A Static Access Method: A public static method, typically named
getInstance(), serves as the sole gateway to the object.
UML Role Diagram
UML Example Diagram
Sequence Diagram
Refining the Solution: Thread Safety and Performance
The “Classic Singleton” implementation uses lazy instantiation, checking if the instance is null before creating it. However, this is not thread-safe; if two threads call getInstance() simultaneously, they might both find the instance to be null and create two separate objects.
There are several ways to handle this in Java:
- Synchronized Method: Adding the
synchronizedkeyword togetInstance()makes the operation atomic but introduces significant performance overhead, as every call to get the instance is forced to wait in a queue, even after the object has already been created. - Eager Instantiation: Creating the instance immediately when the class is loaded avoids thread issues entirely but wastes memory if the object is never actually used during execution.
- Double-Checked Locking: This advanced approach uses the
volatilekeyword on the instance field to ensure it is handled correctly across threads. It checks for anullinstance twice—once before entering a synchronized block and once after—minimizing the performance hit of synchronization to only the very first time the object is created.
Consequences
Applying the Singleton Pattern results in several important architectural outcomes:
- Controlled Access: The pattern provides a single point of access that can be easily managed and updated.
- Resource Efficiency: It prevents the system from being cluttered with redundant, resource-intensive objects.
- The Risk of “Singleitis”: A major drawback is the tendency for developers to overuse the pattern. Using a Singleton just for easy global access can lead to a hard-to-maintain design with high coupling, where it becomes unclear which classes depend on the Singleton and why.
- Complexity in Testing: Singletons can be difficult to mock during unit testing because they maintain state throughout the lifespan of the application. A
static getInstance()call is a hardcoded dependency—there is no seam where a test double can be injected. This is why the pattern is considered an anti-pattern in test-driven development.
A Pattern with a “Weak Solution”
The Singleton is perhaps the most controversial of all GoF patterns. Buschmann et al. (POSA5) describe it as “a well-known pattern with a weak solution”, noting that “the literature that discusses [Singleton’s] issues dwarfs the page count of the original pattern description in the Gang-of-Four book.” The core problem is that the pattern conflates two separate concerns:
- Ensuring a single instance—a legitimate design constraint.
- Providing global access—a convenience that introduces hidden coupling.
Modern practice separates these concerns. A dependency injection (DI) container can manage the singleton lifetime (ensuring only one instance exists) while keeping constructors injectable and dependencies explicit. This gives you the same lifecycle guarantee without the testability and coupling problems.
When Singleton is Acceptable
The Singleton pattern remains acceptable when:
- It controls a true infrastructure resource (e.g., a hardware driver in an embedded system).
- DI is genuinely unavailable (small scripts, legacy code).
- Testability of consuming code is not a concern.
In all other cases, prefer DI with singleton scope. As Feathers puts it: “If your code isn’t testable, it isn’t a good design.”
When Singleton is an Anti-Pattern
- When the “only one” assumption is actually a convenience assumption, not a hard requirement. Many “singletons” later need multiple instances (per-tenant, per-thread, per-test).
- When it is used to create global state—making it impossible to reason about what depends on what.
- When it blocks unit testing by making dependencies invisible and unmockable.
Flashcards
Singleton Pattern Flashcards
Key concepts, controversies, and modern alternatives for the Singleton design pattern.
What are the three implementation aspects of Singleton?
Why is Singleton controversial in modern practice?
Name three thread-safety approaches for Singleton in Java.
What is ‘Singleitis’?
When is Singleton acceptable in modern code?
Quiz
Singleton Pattern Quiz
Test your understanding of the Singleton pattern's controversies, thread-safety mechanisms, and modern alternatives.
POSA5 describes the Singleton as “a well-known pattern with a weak solution.” What is the core reason for this criticism?
Two threads simultaneously call getInstance() on a classic lazy Singleton. Both find uniqueInstance == null and both create a new instance. Which thread-safety approach eliminates this race condition with the simplest implementation and zero per-call overhead — at the cost of not being lazy?
A system uses Singleton for a database connection pool. A new requirement: the system must support multi-tenant deployments with one pool per tenant. What is the fundamental problem?
A developer argues: “Our Logger class uses the Singleton pattern, and it’s fine — we never need to test it.” What is wrong with this reasoning?
Which of the following are legitimate reasons to use the Singleton pattern? (Select all that apply)