Course contentsShow
← Unity Game Development
Lesson 446 of 8287. Advanced Game ArchitectureFree lesson

Implementing a Thread-Safe Singleton Pattern

You will create a robust Singleton base class that ensures only one instance exists while preventing race conditions in multithreaded environments.

Implementing a Thread-Safe Singleton Pattern

In this lesson, you will learn how to build a robust Singleton base class that guarantees a single instance exists while preventing dangerous race conditions in multithreaded environments.

The Problem with Basic Singletons

A standard Singleton often checks if an instance is null and then creates it. In a multithreaded environment, two different threads might perform that null check simultaneously. If both see null at the same time, they will both instantiate the object, resulting in two separate instances—violating the core principle of the pattern.

Using C# Locking

To solve this, we use the lock statement in C#. By using a static readonly object as a gatekeeper, we ensure that only one thread can access the initialization logic at any given time.

Here is how you can implement a thread-safe Singleton base class:

using UnityEngine;

public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T _instance;
    private static readonly object _lock = new object();

    public static T Instance
    {
        get
        {
            lock (_lock)
            {
                if (_instance == null)
                {
                    _instance = (T)FindObjectOfType(typeof(T));

                    if (_instance == null)
                    {
                        GameObject singletonObject = new GameObject();
                        _instance = singletonObject.AddComponent<T>();
                        singletonObject.name = typeof(T).ToString() + " (Singleton)";
                    }
                }
                return _instance;
            }
        }
    }
}

Why This Works

The lock (_lock) block acts as a synchronization primitive. When a thread enters this block, any other thread attempting to access the Instance property must wait until the first thread exits. This prevents the "double-instantiation" bug, ensuring that even if multiple background threads or systems try to call your Singleton simultaneously during startup, the integrity of your instance remains intact.

Note that while this implementation is thread-safe, ensure you only access MonoBehaviour singletons from the Main Thread when interacting with Unity's internal API, as Unity’s engine components are not thread-safe themselves.

Key Takeaway: Use a dedicated lock object to wrap your instance-creation logic to ensure that your Singleton remains a true, single instance regardless of how many threads attempt to access it simultaneously.