线程安全C#单例模式

线程安全C#单例模式,c#,design-patterns,singleton,C#,Design Patterns,Singleton,我对这里记录的单例模式有一些问题: 以下代码摘自本文: using System; public sealed class Singleton { private static volatile Singleton instance; private static object syncRoot = new object(); private Singleton() {} public static Singleton Instance { ge

我对这里记录的单例模式有一些问题:

以下代码摘自本文:

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;
      }
   }
}
具体来说,在上面的示例中,是否需要在锁之前和之后将实例与null进行两次比较?这有必要吗?为什么不先执行锁定并进行比较

简化为以下内容是否有问题

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

         return instance;
      }
   }

执行锁是否昂贵?

原因在于性能。如果
实例!=null
(除第一次外,情况始终如此),无需执行代价高昂的
锁定
:同时访问初始化单例的两个线程将不必要地同步。

在几乎所有情况下(即:除第一次外的所有情况),
实例
不会为null。获取锁的成本比简单的检查要高,因此在锁定之前检查一次
instance
的值是一个不错的免费优化

这种模式称为双重检查锁定:

与简单的指针检查
实例相比,执行锁定的成本非常高!=空

您在这里看到的模式称为。它的目的是避免只需要一次(当第一次访问单例时)的昂贵锁定操作。实现是这样的,因为它还必须确保在初始化singleton时,不会因为线程竞争条件而产生错误

这样想:只有当答案是“是的,对象已经构造好了”时,一个简单的
null
检查(没有
)才能保证为您提供一个正确的可用答案。但是如果答案是“尚未构造”,那么您就没有足够的信息,因为您真正想知道的是它“尚未构造,并且没有其他线程打算很快构造它”。因此,您使用外部检查作为一个非常快速的初始测试,只有在答案为“否”时,您才启动正确的、无错误但“昂贵”的过程(锁定然后检查)


上面的实现对于大多数情况来说已经足够好了,但是在这一点上,最好去阅读它,它也会评估其他替代方案。

执行锁:非常便宜(仍然比空测试更昂贵)

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

当另一个线程拥有锁时执行锁,并且数十个其他线程也在等待它:瘫痪

出于性能方面的考虑,您总是希望在尽可能短的时间内拥有另一个线程想要的锁

当然,“宽”锁比“窄”锁更容易推理,因此有必要从宽锁开始,并根据需要进行优化,但也有一些情况是我们从经验和熟悉中学习到的,窄锁适合这种模式


(顺便说一句,如果您可以只使用
private static volatile Singleton instance=new Singleton()
,或者您可以不使用Singleton而是使用静态类,那么这两种方法都可以更好地解决这些问题)。

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;
            }
        }
    }

Lazy
版本:

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

    public static Singleton Instance
        => lazy.Value;

    private Singleton() { }
}
公共密封类单例
{
私有静态只读惰性
=new Lazy(()=>new Singleton());
公共静态单例实例
=>lazy.Value;
私有单例(){}
}

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

您可以根据应用程序的需要急切地创建线程安全的单例实例,这是简洁的代码,尽管我更喜欢@andasa的懒惰版本

public sealed class Singleton
{
    private static readonly Singleton instance = new Singleton();

    private Singleton() { }

    public static Singleton Instance()
    {
        return instance;
    }
}

这称为双重检查锁定机制,首先,我们将检查实例是否已创建。如果不是,那么我们将同步该方法并创建实例。它将大大提高应用程序的性能。这把锁很重。因此,为了避免锁定,我们首先需要检查null值。这也是线程安全的,也是实现最佳性能的最佳方法。请看一下下面的代码

public sealed class Singleton
{
    private static readonly object Instancelock = new object();
    private Singleton()
    {
    }
    private static Singleton instance = null;

    public static Singleton GetInstance
    {
        get
        {
            if (instance == null)
            {
                lock (Instancelock)
                {
                    if (instance == null)
                    {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
}

Singleton的另一个版本,下面的代码行在应用程序启动时创建Singleton实例

private static readonly Singleton singleInstance = new Singleton();
在这里,CLR(公共语言运行库)将负责对象初始化和线程安全。这意味着我们不需要显式地编写任何代码来处理多线程环境中的线程安全性

“singleton设计模式中的渴望加载在任何情况下都不是一个过程 我们需要在初始化时初始化singleton对象 应用程序启动,而不是按需启动,并将其保存在内存中 以备将来使用。”

从main:

static void Main(string[] args)
        {
            Parallel.Invoke(
                () => PrintTeacherDetails(),
                () => PrintStudentdetails()
                );
            Console.ReadLine();
        }
        private static void PrintTeacherDetails()
        {
            Singleton fromTeacher = Singleton.GetInstance;
            fromTeacher.PrintDetails("From Teacher");
        }
        private static void PrintStudentdetails()
        {
            Singleton fromStudent = Singleton.GetInstance;
            fromStudent.PrintDetails("From Student");
        }

抗反射单态模式:

public sealed class Singleton
{
    public static Singleton Instance => _lazy.Value;
    private static Lazy<Singleton, Func<int>> _lazy { get; }

    static Singleton()
    {
        var i = 0;
        _lazy = new Lazy<Singleton, Func<int>>(() =>
        {
            i++;
            return new Singleton();
        }, () => i);
    }

    private Singleton()
    {
        if (_lazy.Metadata() == 0 || _lazy.IsValueCreated)
            throw new Exception("Singleton creation exception");
    }

    public void Run()
    {
        Console.WriteLine("Singleton called");
    }
}
公共密封类单例
{
公共静态单例实例=>\u lazy.Value;
私有静态惰性_惰性{get;}
静态单态()
{
var i=0;
_懒惰=新懒惰(()=>
{
i++;
返回新的Singleton();
},()=>i);
}
私人单身人士()
{
if(_lazy.Metadata()==0 | | | u lazy.IsValueCreated)
抛出新异常(“单例创建异常”);
}
公开募捐
{
Console.WriteLine(“单例调用”);
}
}

作为旁白,Jon Skeet有一篇关于Singleton中线程安全的精彩文章:最好使用惰性静态init…我在这里也有其他例子解释:与Java world完全相同的问题。我非常喜欢你在这里的想法。这是一个很好的看待它的方式。我希望我能接受两个答案或+5这一个,非常感谢。在考虑性能时,一个重要的结果是,可能同时受到影响的共享结构和将受到影响的共享结构之间的差异。有时候,我们并不期待这样的行为
public sealed class Singleton
{
    public static Singleton Instance => _lazy.Value;
    private static Lazy<Singleton, Func<int>> _lazy { get; }

    static Singleton()
    {
        var i = 0;
        _lazy = new Lazy<Singleton, Func<int>>(() =>
        {
            i++;
            return new Singleton();
        }, () => i);
    }

    private Singleton()
    {
        if (_lazy.Metadata() == 0 || _lazy.IsValueCreated)
            throw new Exception("Singleton creation exception");
    }

    public void Run()
    {
        Console.WriteLine("Singleton called");
    }
}