线程安全C#单例模式

Way*_*pps 74 c# singleton design-patterns

我对这里记录的单例模式有一些疑问:http: //msdn.microsoft.com/en-us/library/ff650316.aspx

以下代码是文章的摘录:

using System;

public sealed class Singleton
{
   private static volatile Singleton instance;
   private static object syncRoot = new object();

   private Singleton() {}

   public static Singleton Instance
   {
      get 
      {
         if (instance == null) 
         {
            lock (syncRoot) 
            {
               if (instance == null) 
                  instance = new Singleton();
            }
         }

         return instance;
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

具体来说,在上面的例子中,是否需要在锁之前和之后将实例与null进行两次比较?这有必要吗?为什么不先执行锁定并进行比较?

简化以下是否有问题?

   public static Singleton Instance
   {
      get 
      {
        lock (syncRoot) 
        {
           if (instance == null) 
              instance = new Singleton();
        }

         return instance;
      }
   }
Run Code Online (Sandbox Code Playgroud)

表演锁是否昂贵?

Jon*_*Jon 121

与简单的指针检查相比,执行锁定非常昂贵instance != null.

您在此处看到的模式称为双重检查锁定.其目的是避免昂贵的锁定操作,这只需要一次(当首次访问单例时).实现是这样的,因为它还必须确保在初始化单例时不会出现线程争用条件导致的错误.

可以这样想:只有当答案为"是,对象已经构建"时,才能保证只有裸null检查(没有a lock)才能给出正确的可用答案.但如果答案是"尚未构建",那么你没有足够的信息,因为你真正想知道的是"它还没有构建,没有其他线程打算很快构建它 ".因此,您使用外部检查作为一个非常快速的初始测试,并且只有在答案为"否"时才启动正确的,无错误但"昂贵"的程序(锁定然后检查).

对于大多数情况,上面的实现已经足够了,但是在这一点上,最好去阅读Jon Skeet关于C#单例的文章,该文章还评估了其他替代方案.

  • 从.NET 4.0开始,`Lazy <T>`就完美地完成了这项工作. (2认同)

小智 28

懒惰的版本:

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy
        = new Lazy<Singleton>(() => new Singleton());

    public static Singleton Instance
        => lazy.Value;

    private Singleton() { }
}
Run Code Online (Sandbox Code Playgroud)

需要.NET 4和C#6.0(VS2015)或更高版本.

  • @WiredEntrepreneur我一直想知道同样的事情,但似乎惰性对象本身是线程安全的:/sf/answers/1065605971/ (2认同)

Jon*_*nna 13

执行锁定:相当便宜(仍然比空测试更昂贵).

当另一个线程拥有它时执行锁定:你得到锁定时他们仍然要做的任何事情的成本,增加到你自己的时间.

当另一个线程拥有它时执行锁定,还有许多其他线程也在等待它:致残.

出于性能原因,您总是希望在最短的时间内拥有另一个线程所需的锁.

当然,对于"宽"锁而不是狭义的推理更容易,因此值得从它们开始并根据需要进行优化,但在某些情况下我们从经验和熟悉中学习,其中较窄的适合模式.

(顺便说一下,如果你可能只是使用private static volatile Singleton instance = new Singleton()或者你可能只是不使用单例而是使用静态类,那么两者在这些问题上都会更好).

  • 当要查看性能时,变得很重要的一个后果是*可以同时被击中的共享结构与*将要被破坏的共享结构之间的差异。有时,我们并不期望这种行为经常发生,但是有可能发生,因此我们需要锁定(锁定只需失败一次,就可以破坏所有内容)。其他时候,我们知道很多线程确实会同时击中相同的对象。但在其他时候,我们并不期望会有很多并发,但是我们错了。当您需要提高性能时,具有大量并发性的优先考虑。 (2认同)

Hei*_*nzi 7

原因是表现.如果instance != null(除了第一次以外总是如此),则不需要付出昂贵的代价lock:同时访问初始化单例的两个线程将不必要地同步.


Eug*_*ski 5

杰弗里·里克特 (Jeffrey Richter) 建议如下:



    public sealed class Singleton
    {
        private static readonly Object s_lock = new Object();
        private static Singleton instance = null;
    
        private Singleton()
        {
        }
    
        public static Singleton Instance
        {
            get
            {
                if(instance != null) return instance;
                Monitor.Enter(s_lock);
                Singleton temp = new Singleton();
                Interlocked.Exchange(ref instance, temp);
                Monitor.Exit(s_lock);
                return instance;
            }
        }
    }

Run Code Online (Sandbox Code Playgroud)