多线程 C# 单例中的问题:未初始化的变量

Issue in C# singleton with multi threading: a variable not intialized

这是生产代码和运行在多线程单线程中的简化版本。 与传统单例相比,额外的事情是我在 lock 部分初始化了客户端。

当我尝试通过Client client = Singleton.Instance.GetClient();获取客户​​时,client有可能为空(但机会很小) .

public class Client
{
    public int Value { get; set; } = 10;
}

public class Singleton
{
    private static Singleton instance = null;
    private static readonly object padlock = new object();
    private Client client = null;

    public static Singleton Instance
    {
        get
        {
            if (instance == null)
            {
                lock (padlock)
                {
                    if (instance == null)
                    {
                        instance = new Singleton();
                        
                        // Here is the interesting part!
                        instance.InitClient();
                    }
                }
            }

            return instance;
        }
    }

    private void InitClient()
    {
        this.client = new Client();
    }

    public Client GetClient()
    {
        return this.client;
    }
}

我是这样测试的:

    static void Main(string[] args)
    {
        Console.WriteLine("Input thread count: ");
        int threadCount = Int32.Parse(Console.ReadLine().Trim());
        List<Task> tasks = new List<Task>(threadCount);

        for (int i = 0; i < threadCount; ++i)
        {
            tasks.Add(Task.Factory.StartNew(() => DoStuff()));
        }

        Task.WaitAll(tasks.ToArray());
        Console.WriteLine("All threads complete");
    }

    private static void DoStuff()
    {
        Client client = Singleton.Instance.GetClient();
        if (client.Value != 10)
        {
            Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}.");
        }
    }

客户端有时可以为空:

但是当我将InitClient()移动到Singleton的私有构造函数中时,我从未遇到过client为null的情况:

private Singleton()
{
    this.InitClient();
}

我不知道有什么区别,有什么问题,感谢您的帮助!

一旦您在锁内调用 instance = new Singleton(),“instance”就不再为 null,这意味着立即对 Singleton.Instance returns 进行单独(线程化)调用,并调用到该实例上的 GetClient 将是与第一次调用的 InitClient 的竞争条件。

在构造函数内部进行初始化可确保“实例”本身在创建后立即进行初始化。因此来自不同线程的后续调用不会与任何东西竞争。