In this post, we are going to discuss How to Implement Thread-Safe Singleton Design Pattern in C# with Examples. Please read our previous article where we discussed Singleton Design Pattern. In that post, the way we have implemented the Singleton Design Pattern is not Thread Safe in a Multithread Environment.
Understand Thread-Safe in Singleton Design Pattern in C#.
Before deep dive in to Thread-Safe Singleton Pattern, let us first see the problem that we face in a multithread environment if the Singleton Class is not Thread-Safe. Below is the Singleton Class which we have created in our last post.
/// <summary> /// Sealed class to ensure that it cannot be inherited /// </summary> public sealed class Singleton { /// <summary> /// To store the Singleton Instance /// </summary> private static Singleton instance = null; /// <summary> /// Counter value will be increment by 1 each time the object of the class is created /// </summary> private static int counter = 0; /// <summary> /// Private constructor to restrict the class to be instantiated from outside the class /// </summary> private Singleton() { counter++; Console.WriteLine("Counter Value " + counter.ToString()); } /// <summary> /// Static Method to return the Singleton Instance /// </summary> public static Singleton Instance() { if (instance == null) { instance = new Singleton(); } return instance; } /// <summary> /// Method to accessed from outside of the class by using the Singleton Instance /// </summary> /// <param name="message"></param> public void PrintDetails(string message) { Console.WriteLine(message); } }
Let’s modify the Program.cs as follows to use multithread programming. As you can see in the below code, we are using Parallel.Invoke method and call PrintCoachDetails() and PrintPlayerDetails() methods parallelly.
Parallel.Invoke(() => PrintCoachDetails(), () => PrintPlayerDetails()); Console.ReadLine(); static void PrintCoachDetails() { //Thread-1 Calling the GetInstance() Method of the Singleton class Singleton fromCoach = Singleton.Instance(); fromCoach.PrintDetails("From Coach"); } static void PrintPlayerDetails() { //At the same time, Thread-2 also Calling the GetInstance() Method of the Singleton Class Singleton fromPlayer = Singleton.Instance(); fromPlayer.PrintDetails("From Player"); }
So in above example, we are using the Parallel.Invoke method to access the Instance() Method parallelly. That means at the same time multiple threads are accessing the Instance() Method. The above code is not Thread-Safe because the way we have written the code here two different threads can evaluate the condition if (instance == null) at the same time and both threads found it to be true and they both will create the instances, which violates the singleton design pattern. So, now run the application and it will give you the following output.
The above output clearly shows that the counter value has incremented to 2, which proves that the constructor of the Singleton class is executed two times as a result two instances of the singleton class have been created. So, if the Singleton class is not Thread-Safe, then we may end up creating multiple instances of the Singleton class in a Multithread Environment.
How to implement a Thread-Safe Singleton Design Pattern in C#?
There are many ways, we can implement the Thread-Safe Singleton Design Pattern in C#. They are as follows.
- Thread-Safety Singleton Implementation using Lock.
- Implementing Thread-Safety Singleton Design Pattern using Double-Check Locking.
- Using Eager Loading to Implement Thread-Safety Singleton Design Pattern.
- Using Lazy<T> Generic Class to Implement Lazy Loading in Singleton Design Pattern.
Here we will discuss how to use Locks and Double-Check Locking Mechanisms to implement the Thread-Safe Singleton Design Pattern in C# which will create only one instance of the Singleton Class in a Multithread environment.
Implementation of Thread-safe Singleton Design Pattern in C# using Locks
Let us discuss how to implement the Thread-Safe Singleton Design Pattern in C# using locks.Using locks we can synchronize the method. So that only one thread can access it at any given point in time. For better understanding, please modify the Singleton Class as follows.
public sealed class SingletonUsingLock { /// <summary> /// To store the Singleton Instance /// </summary> private static SingletonUsingLock instance = null; /// <summary> /// To use the lock, we need to create one variable /// </summary> private static readonly object Instancelock = new object(); /// <summary> /// Counter value will be increment by 1 each time the object of the class is created /// </summary> private static int counter = 0; /// <summary> /// Private constructor to restrict the class to be instantiated from outside the class /// </summary> private SingletonUsingLock() { counter++; Console.WriteLine("Counter Value " + counter.ToString()); } /// <summary> /// Static Method to return the Singleton Instance /// </summary> public static SingletonUsingLock Instance() { lock (Instancelock) { if (instance == null) { instance = new SingletonUsingLock(); } } return instance; } /// <summary> /// Method to accessed from outside of the class by using the Singleton Instance /// </summary> /// <param name="message"></param> public void PrintDetails(string message) { Console.WriteLine(message); } }
In the above code, we lock the shared resource using the lock object and then check whether the instance is created or not.If the instance is already created then we simply return that instance else we will create the instance and then return that instance. The most important point that you need to remember is, as long as one thread locks the resource, no other thread can access the resource.Below is the modified Program.cs file:
using SingletonPattern_ThreadSafe; Parallel.Invoke(() => PrintCoachDetails(), () => PrintPlayerDetails()); Console.ReadLine(); static void PrintCoachDetails() { //Thread-1 Calling the Instance() Method of the Singleton class SingletonUsingLock fromCoach = SingletonUsingLock.Instance(); fromCoach.PrintDetails("From Coach"); } static void PrintPlayerDetails() { //At the same time, Thread-2 also Calling the Instance() Method of the Singleton Class SingletonUsingLock fromPlayer = SingletonUsingLock.Instance(); fromPlayer.PrintDetails("From Player"); }
Now run the application and you will get the following output.
The above code implementation using lock solves the Singleton Design Pattern Thread-Safe issues in a multithreading environment. But the problem is that it is slow down your application as only one thread can access the GetInstance method at any given point in time. We can overcome the above problem by using the Double-Checked Locking mechanism.
Implement Thread-Safe Singleton Design Pattern using Double checked locking
In the Double-Checked Locking implementation for Thread-Safe Singleton Design Pattern in C#, first, we will check whether the instance is created or not. If not then only we will synchronize the method using the lock. For a better understanding, please modify the Singleton class as follows.
public sealed class SingletonUsingDoubleLock { /// <summary> /// To store the Singleton Instance /// </summary> private static SingletonUsingDoubleLock instance = null; /// <summary> /// To use the lock, we need to create one variable /// </summary> private static readonly object Instancelock = new object(); /// <summary> /// Counter value will be increment by 1 each time the object of the class is created /// </summary> private static int counter = 0; /// <summary> /// Private constructor to restrict the class to be instantiated from outside the class /// </summary> private SingletonUsingDoubleLock() { counter++; Console.WriteLine("Counter Value " + counter.ToString()); } /// <summary> /// Static Method to return the Singleton Instance /// </summary> public static SingletonUsingDoubleLock Instance() { if (instance == null) { lock (Instancelock) { if (instance == null) { instance = new SingletonUsingDoubleLock(); } } } return instance; } /// <summary> /// Method to accessed from outside of the class by using the Singleton Instance /// </summary> /// <param name="message"></param> public void PrintDetails(string message) { Console.WriteLine(message); } }
In the above code, we are checking the instance two times whether it is null or not. If it is not null, then we are returning the Singleton instance. If it is null, then we are using the lock to make sure only one thread can enter the critical section. And within the lock block, again we are checking the null condition to make sure only one instance of the Singleton Class is going to be created.
Now run the application and you will see the output as expected as shown in the below image.
The full source code is available here:
Happy coding!! 😊
No comments:
Post a Comment