Singleton Design pattern

The Singleton pattern is a design pattern that restricts the instantiation of a class to a single object. It ensures that only one instance of a class exists in the system and provides a global point of access to that instance. The Singleton pattern falls under the category of creational patterns, as it deals with object creation mechanisms.

Problem it solves?

There are situations where we need to have only one instance of a class throughout the lifetime of an application. For example, a logging class that records all events that occur in the application should only have one instance. If multiple instances of the logging class are created, it can cause problems such as conflicting log entries, loss of log data, and increased memory usage. The Singleton pattern solves this problem by ensuring that only one instance of the class is created and providing a global point of access to it.

How it works ?

The Singleton pattern is implemented by creating a class with a private constructor and a static method that returns the single instance of the class. The class maintains a private static variable that holds the single instance. The static method checks whether an instance of the class has been created or not. If an instance has not been created, it creates one and returns it. If an instance has already been created, it returns the existing instance.

Example in C++

Here is an example of a Singleton class in C++:

class Singleton {
public:
? ? static Singleton* getInstance() {
? ? ? ? if (instance == nullptr) {
? ? ? ? ? ? instance = new Singleton();
? ? ? ? }
? ? ? ? return instance;
? ? }

? ? void someMethod() {
? ? ? ? // implementation
? ? }

private:
? ? static Singleton* instance;
? ? Singleton() {}
    
};

Singleton* Singleton::instance = nullptr;        

In this example, the Singleton class has a private constructor and a static method called getInstance() that returns the single instance of the class. The getInstance() method checks whether the instance variable is nullptr (i.e., whether an instance has been created or not). If an instance has not been created, it creates one and returns it. If an instance has already been created, it returns the existing instance. The class also has a private static variable called instance that holds the single instance.

Any problems with above Singletone class ?

Yes there are few problem, please try to guess!

  1. Does above single instance creation is enough in multi-threaded environment?

No, the above implementation of the Singleton pattern is not thread-safe, and it can lead to several problems in a multi-threaded environment. One of the problems is that two or more threads can simultaneously access the getInstance() method when the instance variable is still nullptr, and they may each create a new instance of the Singleton class. This can result in multiple instances of the Singleton class, which violates the Singleton pattern's requirement of having only one instance of a class.

Another problem is that if one thread has created an instance of the Singleton class while another thread is in the process of creating a new instance, it may end up using an incomplete object that has not been fully initialized. This can result in undefined behavior and cause the program to crash or behave unpredictably.

The non-thread-safe singleton pattern can lead to several problems, including below:

  1. Multiple instances: In a multithreaded environment, multiple threads can create instances of the Singleton class simultaneously, resulting in multiple instances of the Singleton object. This defeats the purpose of the pattern, as the Singleton should only have one instance.
  2. Inconsistent state: When multiple threads access the Singleton instance simultaneously, they may modify its state in unpredictable ways. This can lead to inconsistent behavior and difficult-to-debug errors.
  3. Race conditions: The non-thread-safe Singleton pattern is susceptible to race conditions, where multiple threads try to access or modify the same data simultaneously. This can cause data corruption, deadlock, or other issues.
  4. Deadlocks: Deadlocks can occur when two or more threads are waiting for each other to release a lock, causing them to block indefinitely.
  5. Reduced performance: Synchronization overhead can cause reduced performance in heavily multithreaded applications.

To make the Singleton pattern thread-safe, we need to use some synchronization mechanisms to ensure that only one instance of the Singleton class is created and accessed by multiple threads. One common approach is to use the double-checked locking idiom, which involves checking the instance variable for nullptr inside a critical section and creating a new instance of the Singleton class if it is still nullptr. This ensures that only one thread can create a new instance of the Singleton class, and all subsequent calls to the getInstance() method will return the same instance.

Here's an example of a thread-safe Singleton implementation using the double-checked locking idiom:

class Singleton {
public:
? ? static Singleton* getInstance() {
? ? ? ? if (instance == nullptr) {
? ? ? ? ? ? std::lock_guard<std::mutex> lock(mutex_);
? ? ? ? ? ? if (instance == nullptr) {
? ? ? ? ? ? ? ? instance = new Singleton();
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? return instance;
? ? }

? ? void someMethod() {
? ? ? ? // implementation
? ? }

private:
? ? static Singleton* instance;
? ? static std::mutex mutex_;
? ? Singleton() {}
};


Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex_;        

In the above implementation, we use a std::mutex object to ensure that only one thread can access the critical section where we create a new instance of the Singleton class. The lock_guard object ensures that the mutex is released automatically when the function returns, regardless of whether an exception is thrown or not.

By using a thread-safe Singleton implementation, we can avoid the problems of multiple instances and undefined behavior that can arise in a non-thread-safe implementation.

Is now above singletone class is really Singletone in all scenarios? Is it safe? Can we can ensure single instance in every case?

No, still we can copy the instance returned from getInstance() method to another variable!

2. How to ensure singletone instance by restricting copy of single ton instance:

To ensure that only one instance of the Singleton class is created, it is necessary to prevent copying of the Singleton object. There are two common approaches to achieving this:

  1. Private copy constructor and assignment operator: By declaring the copy constructor and assignment operator as private members of the class, any attempt to copy the object will result in a compile-time error.

Here is an example of how this can be done:

#include <iostream>
#include <thread>
#include <mutex>

using namespace std;

class Singleton {
public:
? ? static Singleton* getInstance() {
? ? ? ? // Lock the mutex
? ? ? ? lock_guard<mutex> lock(mutex_);

? ? ? ? if (instance == nullptr) {
? ? ? ? ? ? instance = new Singleton();
? ? ? ? }
? ? ? ? return instance;
? ? }

? ? void someMethod() {
? ? ? ? // implementation
? ? }

private:
? ? static Singleton* instance;
? ? static mutex mutex_; // declare mutex
? ? Singleton() {}
	? ? // Private copy constructor and assignment operator
? ? Singleton(const Singleton&);
? ? Singleton& operator=(const Singleton&);
};

Singleton* Singleton::instance = nullptr;
mutex Singleton::mutex_; // define mutex

void testSingleton() {
? ? Singleton* instance = Singleton::getInstance();
? ? instance->someMethod();
}

void threadFunction() {
? ? Singleton* instance = Singleton::getInstance();
? ? cout << "Instance address from thread " << this_thread::get_id() << ": " << instance << endl;
}

int main() {
? ? // Create multiple threads to access Singleton
? ? thread t1(testSingleton);
? ? thread t2(testSingleton);
? ? thread t3(testSingleton);
	
	Singleton* instance1 = Singleton::getInstance();
? ? Singleton* instance2 = Singleton::getInstance();
? ? cout << "Instance 1 address: " << instance1 << endl;
? ? cout << "Instance 2 address: " << instance2 << endl;
	
	// Compilation error for below 2 lines which invloke copy constructor
	// Error : Singleton::Singleton(const Singleton&)’ is private within this context
	
	// Singleton instance4 = *instance1;?
	// Singleton instance4(*instance1);

? ? thread t11(threadFunction);
? ? thread t12(threadFunction);
? ? thread t13(threadFunction);

? ? t1.join();
? ? t2.join();
? ? t3.join();
? ? t11.join();
? ? t12.join();
? ? t13.join();

? ? return 0;
}

/*
Op => 
amit@DESKTOP-9LTOFUP:~/OmPracticeC++$ ./a.out
Instance 1 address: 0x7fa358000b70
Instance 2 address: 0x7fa358000b70
Instance address from thread 140339524273728: 0x7fa358000b70
Instance address from thread 140339515819584: 0x7fa358000b70
Instance address from thread 140339507365440: 0x7fa358000b70 
*/         

2. Delete the copy constructor and assignment operator: In C++11 and later, the copy constructor and assignment operator can be deleted using the delete keyword. This prevents any attempt to copy the object at compile-time.

Here is an example of how this can be done :

#include <iostream>
#include <thread>
#include <mutex>

using namespace std;
class Singleton {
public:
? ? static Singleton* getInstance() {
? ? ? ? // Lock the mutex
? ? ? ? lock_guard<mutex> lock(mutex_);

? ? ? ? if (instance == nullptr) {
? ? ? ? ? ? instance = new Singleton();
? ? ? ? }
? ? ? ? return instance;
? ? }

? ? void someMethod() {
? ? ? ? // implementation
? ? }

private:
? ? static Singleton* instance;
? ? static mutex mutex_; // declare mutex
? ? Singleton() {}

? ? // Delete copy constructor and assignment operator
? ? Singleton(const Singleton&) = delete;
? ? Singleton& operator=(const Singleton&) = delete;
};

Singleton* Singleton::instance = nullptr;
mutex Singleton::mutex_; // define mutex

void testSingleton() {
? ? Singleton* instance = Singleton::getInstance();
? ? instance->someMethod();
}

void threadFunction() {
? ? Singleton* instance = Singleton::getInstance();
? ? cout << "Instance address from thread " << this_thread::get_id() << ": " << instance << endl;
}

int main() {
? ? // Create multiple threads to access Singleton
? ? thread t1(testSingleton);
? ? thread t2(testSingleton);
? ? thread t3(testSingleton);
	
	Singleton* instance1 = Singleton::getInstance();
? ? Singleton* instance2 = Singleton::getInstance();
? ? cout << "Instance 1 address: " << instance1 << endl;
? ? cout << "Instance 2 address: " << instance2 << endl;
	
	// Compilation error for below 2 lines which invloke copy constructor
	// Error :? use of deleted function ‘Singleton::Singleton(const Singleton&)’
	
	// Singleton instance4 = *instance1;?
	// Singleton instance4(*instance1);

? ? thread t11(threadFunction);
? ? thread t12(threadFunction);
? ? thread t13(threadFunction);

? ? t1.join();
? ? t2.join();
? ? t3.join();
? ? t11.join();
? ? t12.join();
? ? t13.join();


? ? return 0;
}

/*

Op => 
amit@DESKTOP-9LTOFUP:~/OmPracticeC++$ ./a.out
Instance 1 address: 0x7fa358000b70
Instance 2 address: 0x7fa358000b70
Instance address from thread 140339524273728: 0x7fa358000b70
Instance address from thread 140339515819584: 0x7fa358000b70
Instance address from thread 140339507365440: 0x7fa358000b70 

*/        

Both of the above approaches ensure that the Singleton class cannot be copied or assigned to, which is the desired behavior to maintain the Singleton pattern.

The second approach of using the =delete syntax explicitly deletes the copy constructor and assignment operator, preventing both external code and internal code from calling these methods. This approach provides a more explicit and foolproof solution for enforcing the Singleton pattern and I think is more easy to understand .

Therefore, the second approach of deleting the copy constructor and assignment operator is generally considered to be the better approach for enforcing the Singleton pattern.


In Kotlin, you can create a Singleton using the object keyword. Here's an example:

object Singleton {
? ? init {
? ? ? ? // Initialization code here
? ? }


? ? fun someMethod() {
? ? ? ? // Implementation code here
? ? }
}        

In Kotlin, you can create a Singleton using the object keyword. Here's an example:

You can access the Singleton object in your code like this:

Singleton.someMethod()         

This approach guarantees that only one instance of the Singleton will be created, and it is thread-safe by default.

Advantages

The Singleton pattern has several advantages, including:

  • Global point of access: The Singleton pattern provides a single point of access to the object, which can be accessed from anywhere in the system.
  • Controlled access: The Singleton pattern allows access to the object to be controlled, as only one instance of the class exists.
  • Reduced memory usage: The Singleton pattern reduces memory usage by ensuring that only one instance of the class exists in the system.
  • Simplified object creation: The Singleton pattern simplifies object creation by ensuring that only one instance of the class is created and providing a global point of access to it.

Disadvantages

The Singleton pattern also has some disadvantages, including:

  • Tight coupling: The Singleton pattern can lead to tight coupling between classes, as the Singleton class is globally accessible.
  • Thread safety: The Singleton pattern can be prone to thread safety issues, as multiple threads can attempt to create or access the Singleton instance simultaneously.
  • Testing difficulties: The Singleton pattern can make unit testing difficult, as it is not easy to substitute the Singleton with a mock object during testing.

Manually restricted single instance can be preferred over a Singleton in some cases, especially when it comes to testing. This is because a manually restricted single instance allows for more flexibility and control when creating and managing instances of a class.

With a Singleton, there is typically only one instance of the class available throughout the entire application, which can make it difficult to test certain scenarios that require multiple instances or different configurations of the class.

On the other hand, with a manually restricted single instance, you can create and manage instances of the class as needed, allowing for more flexibility in testing and development. For example, you can create new instances with different configurations or mock instances for testing purposes.

Overall, whether to use a Singleton or a manually restricted single instance depends on the specific requirements and constraints of the application, as well as the preferences and needs of the development team.

Application

The Singleton pattern is used in situations where there is a need for a single instance of a class throughout the lifetime of an application. Some common use cases for Singleton pattern are:

  1. Configuration Management: A Singleton class can be used to manage configuration settings for an application. It can be initialized once during the startup of an application and accessed throughout its lifetime.
  2. Logging: A Singleton class can be used to handle all the logging operations of an application. It ensures that only one instance of the logging object is used throughout the application and provides a central point for logging operations.
  3. Database Connections: A Singleton class can be used to manage database connections in an application. It ensures that only one instance of the database connection object is used throughout the application and provides a central point for managing database connections.
  4. Caching: A Singleton class can be used to manage caching operations in an application. It ensures that only one instance of the cache object is used throughout the application and provides a central point for managing caching operations.
  5. Thread pools: A Singleton class can be used to manage thread pools in an application. It ensures that only one instance of the thread pool object is used throughout the application and provides a central point for managing thread pools.

Overall, the Singleton pattern is useful in situations where there is a need to restrict the instantiation of a class to a single object and provide a global point of access to it.

Thanks for reading till end , Please comment if you have any questions or suggestions.

要查看或添加评论,请登录

社区洞察