单例是否可以仅使用 public 静态字段而不是 Lazy<T> 或其他单例解决方案来安全地初始化线程?

Can a singleton initialize thread-safely just use public static field rather a Lazy<T> or other Singleton solution?

我想创建一个 ConcurrentDictionary 来在我的项目中存储一些数据,并希望在我的应用程序的整个生命周期中保留它,以便不同线程可以访问它以获取缓存数据。

我必须使其初始化线程安全,我不希望它在某些线程同时访问时被创建两次。

我知道静态字段在第一次访问时会被初始化time.So我这样定义ConcurrentDictionary

public class CacheData
{
    public readonly static ConcurrentDictionary<string, string> ConcurrentDic = new ConcurrentDictionary<string, string>();
}

据我所知,它是一个单例,而且是线程安全的,所以现在最让我困惑的是我找不到任何理由使用任何其他解决方案来创建单例 比如 Lazy<ConcurrentDictionary<string, string>> 比如:

public class CacheData
{
    private static Lazy<ConcurrentDictionary<string, string>> _concurrentDic = new Lazy<ConcurrentDictionary<string, string>>(() =>
    {
        return new ConcurrentDictionary<string, string>();
    });

    public static ConcurrentDictionary<string, string> ConcurrentDic
    {
        get
        {
            return _concurrentDic.Value;
        }
    }
}

如果这里Lazy<T>可以完全用静态字段代替,什么时候用Lazy<T>

期待您的回答

首先,您在这里并没有真正使用单例。单例只是特定 class 的唯一实例。 static 字段本身根本没有实例。您的代码中可能有几十个 ConcurrentDictionary,还有许多 class.

实例

static 只是意味着:所有实例 - 无论存在多少实例 - 共享同一个成员,在你的例子中是字典。

除此之外,您的两个方法并不等同,即使在您的特定示例中它们看起来相似。 static readonly 字段和 static Lazy 字段之间最重要的区别是,前者是在第一次使用 class 时初始化的,而后者是在第一次使用 member 时初始化的。很可能在你的整个代码中都没有调用惰性成员,所以它根本不会被初始化。

两个让这个更清楚看这个例子:

public class CacheData
{
    private static readonly int myint = 3;
    private static Lazy<ConcurrentDictionary<string, string>> _concurrentDic = new Lazy<ConcurrentDictionary<string, string>>(() =>
    {
        return new ConcurrentDictionary<string, string>();
    });

    public static ConcurrentDictionary<string, string> ConcurrentDic
    {
        get
        {
            return _concurrentDic.Value;
        }
    }
}

现在你有两个完全独立的成员了。当使用 class 时 myint 被初始化,字典保持未初始化状态。只有当您尝试访问该字典时,它才会被初始化:

class MyClass
{
    void DoSomething()
    {
        // this will initialize myint if not already done
        CacheData.DoSomething(); 
        // however the dictionary is not initialized, only if you try to access it
        console.WriteLine(CacheData.ConcurrentDic["Hello"]);
        // now the dictionary is also initialized
    }
}

长话短说:Lazy 是为了尽可能推迟(重)初始化直到需要时,而 static readonly 字段将在 class 初始化时初始化.

从根本上说,您看到的行为是无法保证的。由于无法保证,因此您不能依赖。以下面的代码为例。它会产生什么输出?答案是这取决于

17.4.5.1: "If a static constructor (§17.11) exists in the class, execution of the static field initializers occurs immediately prior to executing that static constructor. Otherwise, the static field initializers are executed at an implementation-dependent time prior to the first use of a static field of that class."

依赖于实现表示只要遵守规则,多个订单都是有效的。

它可能会生成:

The Dictionary has been initialized now
Start Running...
Start Running...

或者它可能生成:

Start Running...
Start Running...
The Dictionary has been initialized now

您可以在 https://dotnetfiddle.net/d5Tuev 处自行验证(在左侧切换运行时)。

现在,在您的测试中,您已经经历了一个 这些行为。但是这种行为是无法保证的,因此你不应该依赖它。

using System.Collections.Concurrent;
using System.Threading;

namespace Samples.Console
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Thread thread1 = new Thread(() => StaticField.StaticFieldTest());
            Thread thread2 = new Thread(() => StaticField.StaticFieldTest());

            thread1.Start();
            thread2.Start();

            Thread.Sleep(1000);
        }

        public class StaticField
        {
            public static readonly ConcurrentDictionary<string, string> _dic = InitDictionary();

            public static void StaticFieldTest()
            {
                System.Console.WriteLine("Start Running...");

                _dic.TryAdd(string.Empty, string.Empty);
                _dic.TryAdd(string.Empty, string.Empty);
                _dic.TryAdd(string.Empty, string.Empty);
            }

            public static ConcurrentDictionary<string, string> InitDictionary()
            {
                System.Console.WriteLine("The Dictionary has been initialized now");
                Thread.Sleep(500);
                return new ConcurrentDictionary<string, string>();
            }
        }
    }
}

此外 - Lazy 在某些情况下非常有用 - 例如如果您有一个需要 20 秒才能创建的对象,但您希望确保只创建其中一个对象。