Singleton Implementation With Real World Example

Singleton

Singleton is like a single resource which is being shared among multiple users; for example - sharing a single washing machine among all the residents in a hotel or sharing a single appliance like refrigerator among all the family members.

From Wikipedia, "The singleton pattern is a software design pattern that restricts the instantiation of a class to one object.”

I would rather say that singleton pattern ensures that single instance of a class is created and shared among all other objects in an application.

Use Case

Let’s think about the customer who has multiple Windows-based (Desktop) applications currently in use for different financial purposes. Now, they want us to provide a solution to add the printing feature in all the applications they have.

One thing which is clearly stated is that they want printing functionality to be added to all their applications, which means, we need to create a printing library or class that can be shared or accessed from multiple applications.

For such kind of implementation where we have to restrict the instantiation of a class to one object meaning a single instance of a class that can be shared or accessed from multiple, Singleton pattern is the best we can follow. Now, let’s have a look at the implementation.

Classic & Simple Implementation

  1. ///<summary>  
  2. /// This class is the implementation of singleton design pattern which restricts the instantiation of a class to one object  
  3. ///</summary>  
  4. publicsealedclassPrinter {  
  5.     //A static variable which holds a reference to the single created instance  
  6.     privatestaticPrinter _printerInstance;  
  7.     // A static variable which implements a Queue data structure and holds a documents to be printed  
  8.     privatestaticQueue < String > _queue = newQueue < string > ();  
  9.     ///<summary>  
  10.     /// private and parameterless constructor that prevents other classes from instantiating it  
  11.     ///</summary>  
  12.     private Printer() {}  
  13.     ///<summary>  
  14.     /// This method is to get the instance of Printer class  
  15.     ///</summary>  
  16.     ///<param name="instanceName">Name of an instance</param>  
  17.     ///<returns>An instance of Printer class</returns>  
  18.     publicstaticPrinter GetPrinterInstance(string instanceName) {  
  19.         //If there is no instance of printer class exists then instantiate   
  20.         if (_printerInstance == null) {  
  21.             Console.WriteLine("{0} printer object created", instanceName);  
  22.             _printerInstance = newPrinter();  
  23.         }  
  24.         return _printerInstance;  
  25.     }  

The above code works as expected when it is accessed from a single thread or application but there is a possibility that the above code “if (_printerInstance == null)” might not work as expected when accessed from multiple threads or applications meaning when accessed from multiple threads. Simultaneously, the code “if (_printerInstance == null)” might return True for concurrent requests and create separate instances.

Let’s have a look at the request from multiple threads simultaneously and the corresponding response.

  1. staticvoid Main(string[] args) {  
  2.     InitializePrinterObject();  
  3. }  
  4. ///<summary>  
  5. /// This method Initializes three Task and run at the same time with very  
  6. /// less time gap in between. These task initialize Printer object and add document to printer queue  
  7. ///</summary>  
  8. staticvoid InitializePrinterObject() {  
  9.     Printer firstPrinterObject = null;  
  10.     Printer secondPrinterObject = null;  
  11.     Printer thirdPrinterObject = null;  
  12.     Task task1 = Task.Factory.StartNew(() => {  
  13.         firstPrinterObject = InitializePrinterObjectAndAddDocument("First""First-Document");  
  14.     });  
  15.     Task task2 = Task.Factory.StartNew(() => {  
  16.         secondPrinterObject = InitializePrinterObjectAndAddDocument("Second""Second-Document");  
  17.     });  
  18.     Task task3 = Task.Factory.StartNew(() => {  
  19.         thirdPrinterObject = InitializePrinterObjectAndAddDocument("Third""Third-Document");  
  20.     });  
  21.     Task.WaitAll(task1, task2, task3);  
  22.     Console.WriteLine("All threads complete");  
  23.     Console.WriteLine("Are these First Printer Object And Second Printer Object Same - {0} ", firstPrinterObject.Equals(secondPrinterObject) ? "Yes" : "No");  
  24.     Console.WriteLine("Are these First Printer Object And Third Printer Object Same - {0} ", firstPrinterObject.Equals(thirdPrinterObject) ? "Yes" : "No");  
  25.     Console.WriteLine("Are these Second Printer Object And Third Printer Object Same - {0} ", secondPrinterObject.Equals(thirdPrinterObject) ? "Yes" : "No");  
  26.     Console.ReadLine();  
  27. }  
  28. privatestaticPrinter InitializePrinterObjectAndAddDocument(string instanceName, string documentName) {  
  29.     var printerObject = Printer.GetPrinterInstance(instanceName);  
  30.     printerObject.AddDocument(documentName);  
  31.     printerObject.PrintDocument();  
  32.     return printerObject;  
  33. }  

Output

 

As we can see in the above output window that three different instances of Printer object have been created whereas we were expecting only one instance. Also, we can see the documents got added to their instance specific queue. We cannot be sure that we will always get the same output results as this depends on different aspects like no of CPU in the system. It is always better to make the implementation thread-safe to make sure that only one instance is created when accessed from multiple systems simultaneously.

Thread-Safe Implementation

  1. publicsealedclassThreadSafePrinter {  
  2.     //A static variable which holds a reference to the single created instance  
  3.     privatestaticThreadSafePrinter _threadSafePrinterInstance;  
  4.     // A static variable which implements a Queue data structure and holds a documents to be printed  
  5.     privateQueue < String > _queue = newQueue < string > ();  
  6.     privatestaticstring _instanceName;  
  7.     privatestaticobject _syncRoot = newobject();  
  8.     ///<summary>  
  9.     /// private and parameterless constructor that prevents other classes from instantiating it  
  10.     ///</summary>  
  11.     private ThreadSafePrinter() {}  
  12.     ///<summary>  
  13.     /// This method is to get the instance of Printer class  
  14.     ///</summary>  
  15.     ///<param name="instanceName">Name of an instance</param>  
  16.     ///<returns>An instance of Printer class</returns>  
  17.     publicstaticThreadSafePrinter GetPrinterInstance(string instanceName) {  
  18.         //The thread takes out a lock on a shared object, and then checks  
  19.         //whether or not the instance has been created before creating the instance  
  20.         lock(_syncRoot) {  
  21.             //If there is no instance of printer class exists then instantiate   
  22.             if (_threadSafePrinterInstance == null) {  
  23.                 _instanceName = instanceName;  
  24.                 Console.WriteLine("{0} printer object created", instanceName);  
  25.                 _threadSafePrinterInstance = newThreadSafePrinter();  
  26.             }  
  27.         }  
  28.         return _threadSafePrinterInstance;  
  29.     }  
  30. }  
In the above code, we have added a mechanism to lock (keyword in C#) on a static variable which is private to the class. This locking mechanism helps us in blocking the piece of code from another thread to enter or start execution until current thread release lock on the object.

The above code ensures that only one thread will create an instance (as only one thread can be in that part of the code at a time - by the time the second thread enters it. The first thread will have created the instance, so the expression “if (_printerInstance == null)” will evaluate to false) but in this scenario, every thread will acquire a lock ,meaning lock is acquired every time the instance is requested that might lead to a performance issue.

Let’s have a look at the request from multiple threads simultaneously and the corresponding response.

  1. staticvoid Main(string[] args) {  
  2.     InitializeThreadSafePrinterObject();  
  3. }  
  4. ///<summary>  
  5. /// This method Initializes three Task  
  6. /// and run at the same time with very less time gap in between. These task  
  7. /// initialize Printer object and add document to printer queue  
  8. ///</summary>  
  9. staticvoid InitializeThreadSafePrinterObject() {  
  10.     ThreadSafePrinter firstPrinterObject = null;  
  11.     ThreadSafePrinter secondPrinterObject = null;  
  12.     ThreadSafePrinter thirdPrinterObject = null;  
  13.     Task task1 = Task.Factory.StartNew(() => {  
  14.         firstPrinterObject = GetThreadSafePrinterInstance("First""First-Document");  
  15.     });  
  16.     Task task2 = Task.Factory.StartNew(() => {  
  17.         secondPrinterObject = GetThreadSafePrinterInstance("Second""Second-Document");  
  18.     });  
  19.     Task task3 = Task.Factory.StartNew(() => {  
  20.         thirdPrinterObject = GetThreadSafePrinterInstance("Third""Third-Document");  
  21.     });  
  22.     Task.WaitAll(task1, task2, task3);  
  23.     Console.WriteLine("All threads complete");  
  24.     Console.WriteLine("Are these First Printer Object And Second Printer Object Same - {0} ", firstPrinterObject.Equals(secondPrinterObject) ? "Yes" : "No");  
  25.     Console.WriteLine("Are these First Printer Object And Third Printer Object Same - {0} ", firstPrinterObject.Equals(thirdPrinterObject) ? "Yes" : "No");  
  26.     Console.WriteLine("Are these Second Printer Object And Third Printer Object Same - {0} ", secondPrinterObject.Equals(thirdPrinterObject) ? "Yes" : "No");  
  27.     Console.ReadLine();  
  28. }  
  29. privatestaticThreadSafePrinter GetThreadSafePrinterInstance(string instanceName, string documentName) {  
  30.     var printerObject = ThreadSafePrinter.GetPrinterInstance(instanceName);  
  31.     printerObject.AddDocument(documentName);  
  32.     printerObject.PrintDocument();  
  33.     return printerObject;  
  34. }  
Output

 

As we can see in the above output window, the single instance of Printer object is created. Also, we can see the documents got added into the single queue but as mentioned earlier in this scenario, every thread will acquire lock; meaning lock is acquired everytime the instance is requested that might lead to a performance issue.

Let’s have a look at the better thread-safe implementation that reduces the impact on performance because of lock on an object.

Double-Lock Implementation – Better Thread-Safe Implementation

Double-check locking mechanism wherein we add expression “if (_printerInstance == null)” before acquiring lock. With double-check locking mechanism, it is not necessary that the lock is acquired every time the instance is requested which will reduce performance issues but is not completely optimized. The modified implementation looks as mentioned below.

  1. ///<summary>  
  2. /// This method is to get the instance of Printer class  
  3. ///</summary>  
  4. ///<param name="instanceName">Name of an instance</param>  
  5. ///<returns>An instance of Printer class</returns>  
  6. publicstaticThreadSafePrinter GetPrinterInstance(string instanceName) {  
  7.     //If there is no instance of printer class exists then instantiate  
  8.     if (_threadSafePrinterInstance == null) {  
  9.         //The thread takes out a lock on a shared object, and then checks whether or not the instance has been created before creating the instance  
  10.         lock(_syncRoot) {  
  11.             //If there is no instance of printer class exists then instantiate   
  12.             if (_threadSafePrinterInstance == null) {  
  13.                 _instanceName = instanceName;  
  14.                 Console.WriteLine("{0} printer object created", instanceName);  
  15.                 _threadSafePrinterInstance = newThreadSafePrinter();  
  16.             }  
  17.         }  
  18.     }  
  19.     return _threadSafePrinterInstance;  
  20. }  
In the next article, I will talk about the differences between static and singleton. We will see when we can use singleton class over static class.


Similar Articles