Imagine a scenario where only one unique instance of a class needs to exist, acting as a gatekeeper for managing vital resources. This is crucial for optimizing shared resources, like database connections or logging services, ensuring consistency and avoiding conflicts. It also provides centralized control, where a single access point oversees configuration settings or logging, maintaining harmony in system operations. Additionally, it allows for coordinated actions, similar to a conductor leading an orchestra, ensuring that threads, processes, and hardware devices operate in sync. This need for a single, unified instance underlies the Singleton pattern, ensuring control, efficiency, and synchronization across the system’s most critical components.
❗In general, Singleton is considered an anti-pattern and its usage should be limited to very specific rare cases.❗
The pattern
Singleton is creational design pattern. The pattern ensures a class has only one instance and provides a global point of access to that instance.
The Singleton pattern is usually implemented by declaring a private static instance variable within the class, ensuring that only one instance of the class can exist. The class constructor is then made private to prevent external instantiation, allowing instances to be created only from within the class. A public method, often named getInstance()
(or get_instance()
), serves as the global access point to the single instance.
This method checks whether an instance already exists; if not, it creates one. While this basic implementation provides a straightforward way to achieve a single instance, considerations for additional features, such as thread safety and lazy initialization strategies, may be necessary based on specific language requirements or application needs.
Pros
Single Instance: Ensures that a class has only one instance, preventing unnecessary object creation and ensuring global access to that instance.
Global Point of Access: Provides a global point of access to the instance, making it easy to manage and control the unique resource or functionality.
Lazy Initialization: Allows for lazy initialization, meaning the instance is created only when it is first needed, promoting efficiency.
Centralized Control: Facilitates centralized control over a resource or service, making it easier to coordinate actions across the system.
Cons
Global State: Introduces a global state to the application, which can lead to unintended dependencies and make the code less modular.
Hard to Test: Singleton patterns can be challenging to test, especially when they are tightly coupled with other components.
Violates Single Responsibility Principle: The Singleton pattern combines the responsibility of managing the instance and the actual functionality, potentially violating the Single Responsibility Principle.
Difficult to Subclass: Extending a Singleton class can be challenging, as it requires modifying the existing class, potentially leading to a violation of the Open/Closed Principle.
Implementation
Depending on the programming language, the implementation varies a lot and employs additional features like thread safety or lazy initialization strategies for more efficiency.
Java
Here is the most naive singleton implementation in JAVA.
public final class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if(INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Even though the implementation seems to be quite simple, it may suffer from various issues, so we might end up having more than just one instance of the class.
In the absence of synchronization, there’s a possibility that two threads interleave their executions in such a way that the expression INSTANCE == null
evaluates to true for both threads, and as a result, two instances of Singleton get created. Therefore in multithreaded environments, we have to consider synchronization.
public final class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
The class is now thread-safe, however we can see that there is a clear performance issue. Each time we get the instance of our Singleton, we need to acquire a potentially unnecessary lock.
To address the issue, we can verify whether the object needs instantiation in the first place, and only in that case, we take the lock. This approach is called a double-checked locking mechanism.
public final class Singleton {
private static volatile Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if(INSTANCE == null) {
synchronized (Singleton.class) {
if(INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
It is worth mentioning the instance field needs to be volatile to prevent cache incoherence issues. However, the JAVA memory model allows the publication of partially initialized objects and this may lead in turn to very subtle bugs.
Thread safety can be achieved easily using inlined object creation.
public final class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
The above code takes advantage of the fact that static fields and blocks are initialized one after another and creates objects inline. However, there is one drawback of this method. Unfortunately, the Singleton class is initialized eagerly.
To improve initialization, we can introduce laziness with nested classes.
public final class Singleton {
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
A class initialization occurs the first time we use one of its methods or fields, so we can use a nested static class to implement lazy initialization. In this case, the Holder
class will assign the field the first time we access it by invoking getInstance()
.
C++
One way of achieving the desired singleton pattern in C++ is as follows.
class singleton final {
singleton() = default;
singleton(const singleton&) = delete;
singleton(singleton&&) = delete;
singleton& operator=(const singleton&) = delete;
singleton operator=(singleton&&) = delete;
static singleton* instance;
public:
static singleton& get_instance() {
if (instance == nullptr) {
instance = new singleton;
}
return *instance;
}
};
singleton* singleton::instance;
The provided C++ code defines a singleton class with a private constructor and deleted copy and move constructors, as well as copy and move assignment operators, enforcing the Singleton pattern by preventing the creation of multiple instances. A static variable named instance
holds the singleton instance. The public section exposes a static method, get_instance()
, which ensures that only one instance of the singleton class exists. If the instance
pointer is nullptr
, it creates a new instance using the private constructor. The method then returns a reference to the singleton instance. This implementation allows for lazy instantiation, creating the instance only when needed, but it's worth noting that it lacks thread safety. In a multithreaded environment, additional synchronization mechanisms would be required to ensure a single, consistent instance.
class singleton final {
singleton() = default;
singleton(const singleton&) = delete;
singleton(singleton&&) = delete;
singleton& operator=(const singleton&) = delete;
singleton operator=(singleton&&) = delete;
static singleton* instance;
static std::mutex lock;
public:
static singleton& get_instance() {
std::lock_guard guard(lock);
if (instance == nullptr) {
instance = new singleton;
}
return *instance;
}
};
singleton* singleton::instance;
std::mutex singleton::lock;
In the slightly updated code, a static std::mutex
named lock
has been introduced in the Singleton class. This addition enhances thread safety by incorporating a mutex to synchronize access to the critical section of the get_instance()
method. The std::lock_guard
is employed to manage the lock automatically, ensuring that only one thread at a time can execute the instantiation logic within the conditional block. This modification addresses potential race conditions in a multithreaded environment, making the Singleton pattern implementation more robust and preventing the creation of multiple instances concurrently, but may also introduce performance issues. We can try to do a double-checked locking trick to improve it.
class singleton final {
singleton() = default;
singleton(const singleton&) = delete;
singleton(singleton&&) = delete;
singleton& operator=(const singleton&) = delete;
singleton operator=(singleton&&) = delete;
static singleton* instance;
static std::mutex lock;
public:
static singleton& get_instance() {
if (instance == nullptr) {
std::lock_guard guard(lock);
if (instance == nullptr) {
instance = new singleton;
}
}
return *instance;
}
};
singleton* singleton::instance;
std::mutex singleton::lock;
In the updated code, the modification involves moving the std::lock_guard
inside the first null check in the get_instance()
method. This adjustment addresses potential race conditions more efficiently. Placing the lock guard around the entire instantiation block ensures that only one thread at a time can proceed to check and create the instance. This change enhances thread safety by preventing multiple threads from entering the critical section concurrently, offering a more robust solution in a multithreaded environment. The std::lock_guard
now covers the entire section where the instance is created, providing a clearer and safer synchronization mechanism.
Another way of implementing Singleton utilizes the std::call_once
library function.
class singleton final {
singleton() = default;
singleton(const singleton&) = delete;
singleton(singleton&&) = delete;
singleton& operator=(const singleton&) = delete;
singleton operator=(singleton&&) = delete;
static singleton* instance;
static std::once_flag flag;
public:
static singleton& get_instance() {
std::call_once(flag, [] { instance = new singleton; });
return *instance;
}
};
singleton* singleton::instance;
std::once_flag singleton::flag;
In this modified code, the std::mutex
has been replaced with std::once_flag
, introducing a more efficient and concise mechanism for ensuring thread-safe, one-time initialization of the singleton instance. The std::call_once
function is employed in the get_instance()
method, which takes a std::once_flag
and a callable object (here, a lambda function) as arguments. This function ensures that the enclosed code block, responsible for creating the singleton instance, is executed only once, regardless of the number of threads accessing it concurrently. The use of std::once_flag
simplifies the implementation, removing the need for explicit lock guards and offering a more idiomatic and efficient solution to achieve thread safety in a Singleton pattern.
Here is a simple yet powerful Singleton implementation in C++ language.
class singleton final {
singleton() = default;
singleton(const singleton&) = delete;
singleton(singleton&&) = delete;
singleton& operator=(const singleton&) = delete;
singleton operator=(singleton&&) = delete;
public:
static singleton& get_instance() {
static singleton singleton;
return singleton;
}
};
The provided C++ code defines Meyer's Singleton class, ensuring that only one instance of this class can exist within the system. The class has a private constructor, preventing external instantiation. Copy and move constructors, as well as copy and move assignment operators, are explicitly deleted, reinforcing the Singleton pattern by disallowing the creation of new instances through these means. The public section exposes a static method, get_instance()
, which returns a reference to a static, local instance of the singleton class. This instance is created on the first call to get_instance()
and persists throughout the program's execution. By combining the private constructor and static method, the class guarantees that only a single instance is accessible globally, due to §6.7 [stmt.dcl] p4 in C++11 language standard:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
the need for manual locking to ensure thread safety is no longer required.
❗Before the C++11 language standard, the above implementation was susceptible to race conditions in multi-threaded environments.❗
Unfortunately, this implementation doesn’t avoid the static (de)initialization order fiasco.
Python
In Python, the absence of true private constructors challenges the conventional implementation of design patterns like the Singleton pattern. Unlike languages that support strict access control such as C++, Python relies on developer conventions (leading underscore _
) rather than enforceable privacy mechanisms. In the context of the Singleton pattern, which typically involves restricting instantiation to a single instance, the lack of private constructors means that other strategies must be employed to achieve this constraint.
Python's metaclasses represent a potent but advanced feature through which we can customize the creation of classes. Essentially, a metaclass is a class of a class. When we define a class, the interpreter utilizes a metaclass to bring that class into existence. The default metaclass is typically the built-in type
. Metaclasses empower us to control the behavior of class creation, giving us the ability to modify class attributes, methods, or even the overall structure of a class. This provides us with a means to enforce coding standards, perform code injections, or implement design patterns.
To define a metaclass, we create a class that inherits from the built-in type
class. Then we can override certain methods to customize the class creation process.
class SingletonMetaclass(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
The provided code defines a metaclass named SingletonMetaclass
. It inherits from the built-in type
. It is designed to implement the Singleton pattern, ensuring that only one instance of a class using this metaclass exists. The metaclass overrides the __call__
method, intercepting the instantiation process. Within this method, it checks if an instance of the class already exists in the _instances
dictionary. If not, it creates a new instance using super().__call__(*args, **kwargs)
and stores it in the dictionary. Subsequent calls to instantiate the class return the existing instance from the dictionary, effectively enforcing the Singleton pattern by allowing only one instance of the class to be created throughout the program’s execution.
class MySingleton(metaclass=SingletonMetaclass):
def foo(self):
pass
By using SingletonMetaclass
metaclass, the MySingleton
class is designed to adhere to the Singleton pattern, ensuring that only one instance of MySingleton
exists. Any attempt to instantiate MySingleton
will be handled by the associated metaclass, guaranteeing that subsequent calls to create an instance return the same singular instance.
An alternative approach to implementing the Singleton pattern involves using a decorator function.
def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
The provided code snippet defines a decorator named singleton
, which takes a class cls
as an argument. The decorator returns a wrapper function that controls the instantiation of the class. The wrapper function, when applied to a class, checks if an instance of that class already exists in the instances
dictionary. If not, it creates a new instance using cls(*args, **kwargs)
and stores it in the dictionary. Subsequent calls to instantiate the class return the existing instance from the dictionary, ensuring that only one instance of the class is created.
@singleton
class MySingleton():
def foo(self):
pass
This method offers a concise and readable way to selectively apply the Singleton pattern to classes in Python, though it may not provide the same level of control and sophistication as metaclasses.
Utilizing modules as an alternative approach to implementing the Singleton pattern in Python taps into the language's inherent module system to naturally achieve a single instance. Given that modules are imported only once per interpreter session, they inherently act as a global singleton, making them an efficient and straightforward solution. By consolidating shared resources or states within a module, subsequent imports across the application implicitly reference the same instance, offering a de facto Singleton behavior.
Summary
The Singleton pattern, in principle, is straightforward, aiming to ensure a class has only one instance and providing a global point of access to that instance. However, its implementation often proves tricky and cumbersome, involving complex mechanisms for ensuring thread safety and one-time instantiation. This pattern introduces a level of coupling in code, making it challenging to test and maintain. While the pattern may seamlessly align with one programming language, its applicability might not extend as smoothly to others. Due to these complexities and drawbacks, the Singleton pattern is considered an antipattern, and its usage is recommended only in very specific and limited cases.