ASP.NET Core  

How to Implement Thread-Safe Singleton Design Pattern with Lazy

Introduction

The Singleton Design Pattern is one of the most commonly used design patterns in C#. It ensures that a class has only one instance and provides a global access point to that instance.

However, in multi-threaded applications, creating a thread-safe Singleton becomes very important. If not handled correctly, multiple threads can create multiple instances, which breaks the purpose of Singleton.

In this article, we will learn how to implement a thread-safe Singleton using Lazy in simple words.

What is Singleton Design Pattern?

Singleton means:

  • Only one instance of a class should exist

  • That instance should be globally accessible

Example use cases:

  • Logging service

  • Configuration manager

  • Database connection manager

Basic idea:
You don’t allow users to create objects directly. Instead, you control the instance creation inside the class.

Problem with Traditional Singleton (Not Thread-Safe)

Let’s see a simple Singleton example:

public class Singleton
{
    private static Singleton _instance;

    private Singleton() { }

    public static Singleton Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new Singleton();
            }
            return _instance;
        }
    }
}

Problem:

  • If two threads access Instance at the same time

  • Both may create a new object

Result:
❌ Multiple instances created

This breaks thread safety.

What is Thread Safety?

Thread safety means that your code works correctly even when multiple threads run at the same time.

In Singleton:

  • Only one instance should be created

  • No matter how many threads access it

Traditional Fix: Locking (But Not Efficient)

private static readonly object _lock = new object();

public static Singleton Instance
{
    get
    {
        lock (_lock)
        {
            if (_instance == null)
            {
                _instance = new Singleton();
            }
            return _instance;
        }
    }
}

Problem:

  • Locking is slow

  • Performance overhead

This is where Lazy helps.

What is Lazy in C#?

Lazy is a built-in class in .NET that ensures:

  • Object is created only when needed (lazy initialization)

  • Thread-safe by default

Simple meaning:
"Create the object only when someone asks for it, and do it safely."

How Lazy Solves Thread Safety

Lazy handles:

  • Thread synchronization internally

  • Ensures only one instance is created

  • Avoids unnecessary locking

So, we don’t need to write complex thread-safe code.

Implementing Thread-Safe Singleton Using Lazy

Here is the best and clean approach:

public sealed class Singleton
{
    private static readonly Lazy<Singleton> _instance =
        new Lazy<Singleton>(() => new Singleton());

    private Singleton() { }

    public static Singleton Instance => _instance.Value;
}

Explanation (Step-by-Step)

Let’s understand this code:

  1. Lazy holds the instance

  2. The lambda () => new Singleton() defines how to create it

  3. Object is NOT created immediately

  4. It is created only when Instance is accessed

  5. Lazy ensures thread safety automatically

This makes the code simple, clean, and efficient.

Why Use Sealed Class?

public sealed class Singleton

Reason:

  • Prevents inheritance

  • Ensures no other class can extend and break Singleton behavior

Benefits of Using Lazy Singleton

  • Thread-safe by default

  • Lazy initialization (better performance)

  • No need for manual locks

  • Cleaner and more readable code

Real-World Example

Let’s create a Logger using Singleton:

public sealed class Logger
{
    private static readonly Lazy<Logger> _instance =
        new Lazy<Logger>(() => new Logger());

    private Logger() { }

    public static Logger Instance => _instance.Value;

    public void Log(string message)
    {
        Console.WriteLine($"Log: {message}");
    }
}

Usage:

Logger.Instance.Log("Application started");
Logger.Instance.Log("User logged in");

No matter how many times you call Instance, only one Logger object is created.

Common Mistakes Developers Make

Not Making Constructor Private

If constructor is public:

var obj = new Singleton();

❌ Breaks Singleton

Always make constructor private.

Not Using Lazy Initialization

Creating instance at startup wastes memory if not used.

Lazy solves this problem.

Overusing Singleton

Singleton should not be used everywhere.

Use it only when:

  • You truly need a single shared instance

  • Global access is required

Overuse can lead to tight coupling.

Ignoring Thread Safety

In multi-threaded apps, ignoring thread safety can cause bugs that are hard to debug.

Always prefer Lazy or other safe approaches.

When to Use Singleton Pattern

Use Singleton when:

  • You need a single shared resource

  • Configuration should be consistent

  • Logging should be centralized

Avoid when:

  • You need multiple instances

  • Testing becomes difficult

Summary

The Singleton Design Pattern ensures that only one instance of a class exists. However, in multi-threaded applications, making it thread-safe is crucial. Using Lazy in C# is the best and simplest way to implement a thread-safe Singleton. It provides built-in thread safety, lazy initialization, and cleaner code without using locks. By understanding this approach, developers can build efficient, scalable, and reliable .NET applications.