Thread Safe Singleton using Lock

Singleton Design Pattern is one of the creational design patterns “that ensures a class has only one instance, while providing a global access point to this instance”. We have seen the basic example about its implementation, but it can also create multiple instances of object if there are multiple threads trying instantiate the same object.

To fix this, we can use a thread-safe Singleton implementation using the lock keyword which is a common approach to ensure that only one instance of the Singleton class is created even in a multi-threaded environment. This implementation uses a locking mechanism to manage concurrent access.

The "lock" approach ensures that only one thread can access the critical section of code that creates the singleton instance, preventing multiple instances from being created. Here’s a simple implementation of the thread-safe singleton design pattern in C# using a lock:

Thread-Safe Singleton - Single Lock Implementation



using System;

public class Singleton
{
    private static Singleton _instance;
    private static readonly object _lock = new object();

    // Private constructor to prevent instantiation from outside
    private Singleton()
    {
        // Initialization code here
    }

    public static Singleton Instance
    {
        get
        {
	     // Lock to ensure thread safety
             lock (_lock)
             {
                 if (_instance == null)
                 {
                     _instance = new Singleton();
                 }
             }
          return _instance;
        }
    }

    public void SomeBusinessLogic()
    {
        // Business logic goes here
        Console.WriteLine("Executing business logic...");
    }
}

class Program
{
    static void Main(string[] args)
    {
         // Access the singleton instance and call a method
         Singleton singleton1 = Singleton.Instance;
         Singleton singleton2 = Singleton.Instance;
         singleton1.SomeBusinessLogic();
         singleton2.SomeBusinessLogic();
    }
}

Explanation:
  • Private Constructor: The constructor of the Singleton class is private to prevent instantiation from outside the class.

  • Static Variables:
    • _instance: Holds the single instance of the class.
    • _lock: A static readonly object used for locking to ensure thread safety.

  • Instance Property:
    • The Instance property checks if _instance is null.
    • If it is null, it acquires a lock on _lock to prevent other threads from entering this critical section.

  • Business Logic Method: An example method SomeBusinessLogic is provided to demonstrate how to use the singleton.As you can see two instances are called for Singleton Class but after execution, only one instance will be created and SomeBusinessLogic gets called twice.

Thread-Safe Singleton - Double Lock Implementation

The only difference in single lock and double lock is to add one more if condition for null check for _instance variable


 using System;

 public class  Singleton
{
    private static Singleton_instance;
    private static readonly object _lock = new object();

    // Private constructor to prevent instantiation from outside
    private Singleton()
    {
        // Initialization code here
    }

    public static Singleton Instance
    {
        get
        {
            // Check if instance is null
            if (_instance == null)
            {
               // Lock to ensure thread safety
                lock (_lock)
                {
                    // Double-check to ensure instance is still null
                    if(_instance == null)
                    {
                        _instance = new Singleton();
                    }
                }
            }
            return _instance;
        }
    }

    public void SomeBusinessLogic()
    {
        // Business logic goes here
        Console.WriteLine("Executing business logic...");
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Access the singleton instance and call a method
        Singleton singleton1 = Singleton.Instance;
        Singleton singleton2 = Singleton.Instance;
        singleton1.SomeBusinessLogic();
        singleton2.SomeBusinessLogic();

    }
}



Summary

These implementations ensures that the Singleton instance is created in a thread-safe manner. It utilizes a lock to handle concurrent access, ensuring that only one instance is created even when multiple threads are used. The double-check locking pattern helps optimize performance by reducing the overhead of locking once the instance is already initialized.