Thread-Safe Singleton in C# with LazyThreadSafetyMode

Implementing a robust, thread-safe singleton pattern in modern C# is most effectively achieved using the built-in Lazy<T> type configured with LazyThreadSafetyMode. This article breaks down the mechanics of the LazyThreadSafetyMode enumeration, compares its different options, and provides a clear, production-ready implementation of a thread-safe singleton.

Understanding LazyThreadSafetyMode

The Lazy<T> class provides lazy initialization, deferring the creation of an object until it is first requested. When using Lazy<T> across multiple threads, the LazyThreadSafetyMode enum determines how thread synchronization is handled:

  1. ExecutionAndPublication: Only a single thread is permitted to execute the initialization factory method. All other threads block until the initialization completes, ensuring that the initialization delegate runs exactly once and all threads share the resulting instance.
  2. PublicationOnly: Multiple threads can execute the initialization delegate concurrently if they access the instance simultaneously. However, only the first thread to complete initialization successfully publishes its instance; the other instances are discarded. This avoids locks at the cost of potential redundant object allocations.
  3. None: No thread synchronization is performed. If accessed across multiple threads, behavior is undefined and race conditions will occur.

For the vast majority of singleton implementations, LazyThreadSafetyMode.ExecutionAndPublication is the correct choice because it guarantees that the constructor or factory function is called only once.

Implementing the Singleton Pattern

To build a thread-safe singleton, declare a private static readonly Lazy<T> field, specify a private constructor to prevent external instantiation, and expose a public static property to access the instance.

public sealed class SingletonService
{
    private static readonly Lazy<SingletonService> _instance =
        new Lazy<SingletonService>(
            () => new SingletonService(), 
            LazyThreadSafetyMode.ExecutionAndPublication
        );

    // Private constructor prevents external instantiation
    private SingletonService()
    {
        // Initialization logic here
    }

    // Public accessor
    public static SingletonService Instance => _instance.Value;

    public void ExecuteOperation()
    {
        // Business logic here
    }
}

Key Considerations

  • Default Behavior: If you construct new Lazy<T>(() => new T(), true), the boolean true defaults internally to LazyThreadSafetyMode.ExecutionAndPublication. Explicitly passing the enum value improves code clarity and intent.
  • Exception Handling: When using ExecutionAndPublication, if the initialization delegate throws an unhandled exception, that exception is cached. Subsequent attempts to read Value will immediately rethrow the same exception without attempting initialization again.
  • Double-Checked Locking: Using Lazy<T> eliminates the need to write custom double-checked locking logic with explicit lock statements, reducing the risk of subtle concurrency bugs and volatile read/write mistakes.