patron-singleton

Singleton Pattern: Usage, Risks, and Thread Safety

  • 5 min

The Singleton is a pattern that restricts a class to a single instance and provides a shared access point. It is probably the first creational pattern many people learn because its structure is simple.

It is likely the first pattern every programmer learns. Why? Because its concept is very easy to understand and, at first glance, it seems like the solution to all our data access problems.

But be careful. The Singleton is a double-edged sword. So much so that many architects today consider it an anti-pattern.

In this article, we will see what it is, how to implement it correctly (and safely for threads) in C# and C++, and when we should steer clear of it.

What is the Singleton?

The premise of the Singleton is simple: guarantee a single instance within the application’s execution scope and provide access to it. It does not guarantee a single instance across multiple processes, containers, or servers.

Imagine you have a class that manages the global application configuration, or a Logging system. It makes no sense (and can be dangerous) to have 50 Logger objects fighting to write to the same file, or 20 copies of a configuration that should be unique.

We want to ensure that only one exists. Like in The Highlanders: “There can be only one”.

Implementation in C#

In C#, the naive implementation would be to check if the variable is null, and if so, create it. But this doesn’t work in multi-threaded environments.

If two threads call GetInstance() at the same time and the instance is null, they will both enter the if block and create two different instances. Goodbye Singleton.

Here is a robust, Thread-Safe, and modern implementation using Lazy<T>, which is the recommended approach in current .NET.

public sealed class Singleton
{
    // Using Lazy<T> to guarantee lazy initialization and Thread-Safety
    private static readonly Lazy<Singleton> _instance =
        new Lazy<Singleton>(() => new Singleton());

    // PRIVATE Constructor: Prevents instantiation from outside
    private Singleton()
    {
        Console.WriteLine("Singleton initialized");
    }

    // Public property for global access
    public static Singleton Instance
    {
        get
        {
            return _instance.Value;
        }
    }

    // Example business method
    public void DoSomething()
    {
        Console.WriteLine("Hello from the Singleton!");
    }
}

// Usage:
// Singleton s1 = Singleton.Instance;
Copied!

We use sealed to prevent the class from being inherited, which could complicate the instance’s uniqueness.

Implementation in C++

In classic C++, the Singleton was a headache due to concurrency issues (the famous Double-Checked Locking).

However, since C++11, the standard guarantees that local static variables are initialized safely in multi-threaded environments the first time execution flow passes through them.

This is known as the Meyers’ Singleton (by Scott Meyers) and is the canonical way to do it today. Elegant and efficient.

class Singleton {
public:
    // Delete copy constructor and assignment operator
    // to prevent accidental copies.
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // Static access method
    static Singleton& GetInstance() {
        // This static variable is created the first time GetInstance is called
        // and is Thread-Safe since C++11.
        static Singleton instance;
        return instance;
    }

    void DoSomething() {
        // ... logic ...
    }

private:
    // PRIVATE Constructor
    Singleton() {
        // Initialization...
    }
};

// Usage:
// Singleton& s1 = Singleton::GetInstance();
Copied!

Note the = delete. In C++, it’s advisable to prohibit copying; otherwise, someone could do Singleton s2 = s1; and break uniqueness.

The Dark Side: Why is it an Anti-Pattern?

If it’s so useful, why does it have such a bad reputation?

The problem isn’t the pattern itself, but its abuse. A Singleton is, in essence, a glorified global variable. And we all know that global variables are bad.

Hidden Coupling

Look at this code:

public void ProcessOrder()
{
    // ... logic ...
    Logger.Instance.Log("Order processed");
}
Copied!

If you look at the signature of ProcessOrder, it seems it doesn’t need anything external. The dependency is hidden because the method accesses the Logger globally.

This violates the principle that dependencies should be explicit. If we change the Logger tomorrow, we have to track down all the code where Instance is used.

The Unit Testing Nightmare

This is the biggest problem. Singletons are very difficult to test.

Being global and static, they maintain their state between tests. If one test modifies the Singleton, it can cause the next test, which expects the Singleton to be “clean,” to fail. Furthermore, it’s nearly impossible to mock a static method to simulate behaviors.

Parallelization Problems

Having a single access point can become a bottleneck if many threads try to access the shared resource at the same time, forcing us to use locks that degrade performance.

When to use it then?

The Singleton has its place, but it’s best used in moderation:

  • Logging: Having a single access point for writing logs is usually acceptable.
  • Cache / Configuration: If you have a configuration object loaded at startup that is read-only, a Singleton can be practical.
  • Hardware Access: If you are programming a driver for a single physical device (e.g., a specific serial port), the Singleton ensures there aren’t two controllers trying to talk to the hardware at the same time.