Implementing Singleton Design Pattern
Introduction
The Singleton design pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to that instance. In simpler terms, it ensures that a particular class has only one object (instance) created from it and provides a way to access that object throughout the application. The Singleton pattern is used when you want to control access to a shared resource or when you need a single point of control for certain operations.
Here are the key characteristics and use cases of the Singleton design pattern:
- Single Instance: The Singleton pattern guarantees that there is only one instance of the class created and that it can be easily accessed.
- Global Access: It provides a global point of access to that instance so that other parts of the application can use it easily.
- Lazy Initialization: The Singleton instance is typically created only when it is first needed (lazy initialization) rather than when the class is loaded, which can help improve performance and resource usage.
- Thread Safety: In multithreaded environments, Singleton implementations often include mechanisms to ensure that only one instance is created even if multiple threads attempt to create it simultaneously. Common approaches include using locks or synchronization.
- Examples of Use Cases:
- Managing Configuration Settings: Singletons can be used to store and manage configuration settings for an application. This ensures that the configuration data is loaded and accessed consistently.
- Database Connection Pooling: In scenarios where you want to limit the number of database connections, a Singleton pattern can be used to manage a pool of database connections.
- Logging: Singletons can centralize logging functionality, allowing all parts of the application to log messages using a shared logging instance.
- Caching: To implement a cache that stores frequently used data, a Singleton pattern can be used to manage the cache and ensure there’s only one instance of it.
Here is an example in C++
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
class Singleton {
private:
Singleton() { ++m_cnt; }
Singleton(Singleton const&);
public:
static int m_cnt;
static Singleton *m_instance;A
static Singleton *getInstance();
static std::mutex m_mtx;
void showMessage() { std::cout << "Instance count: " << m_cnt << std::endl; }
};
Singleton *Singleton::getInstance() {
if (m_instance == nullptr)
{
m_mtx.lock();
if (m_instance == nullptr) {
m_instance = new Singleton();
}
m_mtx.unlock();
}
return m_instance;
}
int Singleton::m_cnt = 0;
Singleton *Singleton::m_instance = nullptr;
std::mutex Singleton::m_mtx;
void objectCreationFunction() {
Singleton *instance = Singleton::getInstance();
instance->showMessage();
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 100; ++i)
threads.push_back(std::thread(objectCreationFunction));
for (auto &thread : threads)
thread.join();
return 0;
}
Explanation
- Singleton Class Definition: The code defines a
Singleton
class with the following components:
- Private constructor: The constructor is private, ensuring that the
Singleton
class cannot be instantiated from outside the class. - Private copy constructor: The copy constructor is declared but not implemented, preventing the class from being copied.
- Static variables:
m_cnt
: An integer variable to keep track of the number of instances created.m_instance
: A pointer to the single instance of theSingleton
class.m_mtx
: Astd::mutex
object for thread safety.
2. Singleton::getInstance() Method: This method is used to retrieve the single instance of the Singleton
class. It follows the double-checked locking pattern for efficient multithreaded initialization.
- It checks if
m_instance
isnullptr
. - If it is
nullptr
, it locks them_mtx
mutex to ensure exclusive access. - Inside the locked section, it checks
m_instance
again (double-check) to make sure it wasn't already created by another thread. - If it’s still
nullptr
, it creates a new instance of theSingleton
class and assigns it tom_instance
. - Finally, it unlocks the mutex and returns
m_instance
.
3. Global Variables and Mutex Initialization: The code initializes the static variables m_cnt
, m_instance
, and m_mtx
.
4. objectCreationFunction(): This function is used by multiple threads to create and access the Singleton instance. It:
- Calls
Singleton::getInstance()
to retrieve the instance. - Calls
showMessage()
to display the current instance count.
5. Main Function:
- It creates a vector of threads (
std::vector<std::thread> threads
) to simulate multiple threads concurrently accessing the Singleton instance. - In a loop, it launches 100 threads (I know 100 is too much, not every machine can support these many threads), each running the
objectCreationFunction
. - After all threads have been completed, it joins them to ensure the main thread waits for them to finish.
- The purpose is to demonstrate that even with concurrent access from multiple threads, only one instance of the
Singleton
class is created, and the instance count is incremented accordingly.
The code effectively implements the Singleton pattern with thread safety to ensure that only one instance of the Singleton
class is created, even in a multithreaded environment. Each thread accesses the same instance, and the instance count is incremented to demonstrate the shared access.
I hope this information helps you. Thanks for reading.
Kinshuk