线程安全的 C# 单例模式

Posted

技术标签:

【中文标题】线程安全的 C# 单例模式【英文标题】:Thread Safe C# Singleton Pattern 【发布时间】:2012-09-01 05:41:18 【问题描述】:

我对这里记录的单例模式有一些疑问: 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;
      
   

具体来说,在上面的例子中,是否需要在锁之前和之后将 instance 与 null 进行两次比较?这是必要的吗?为什么不先加锁再做比较呢?

简化成下面有问题吗?

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

         return instance;
      
   

执行锁定是否昂贵?

【问题讨论】:

顺便说一句,Jon Skeet 有一篇关于单例线程安全的精彩文章:csharpindepth.com/Articles/General/Singleton.aspx 懒惰的静态初始化会更好...... 我这里还有其他的例子和解释:csharpindepth.com/Articles/General/Singleton.aspx 与 Java 世界完全相同的问题 here。 【参考方案1】:

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

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

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

上述实现对于大多数情况来说已经足够好了,但此时最好阅读Jon Skeet's article on singletons in C#,它还评估了其他替代方案。

【讨论】:

感谢您提供有用链接的信息回复。非常感谢。 双重检查锁定 - 链接不再起作用。 对不起,我指的是另一个。 @ElMac:Skeet 的网站在 ATM 上停机,将在适当的时候恢复。我会记住这一点,并确保链接在出现时仍然有效,谢谢。 从 .NET 4.0 开始,Lazy<T> 完美地完成了这项工作。【参考方案2】:

Lazy<T> 版本:

public sealed class Singleton

    private static readonly Lazy<Singleton> lazy
        = new Lazy<Singleton>(() => new Singleton());

    public static Singleton Instance
        => lazy.Value;

    private Singleton()  

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

【讨论】:

@ttugates,你是对的,谢谢。使用惰性对象的值工厂回调更新代码。【参考方案3】:

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

当另一个线程拥有它时执行锁定:您将获得他们在锁定期间仍然要做的任何事情的成本,添加到您自己的时间中。

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

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

当然,“宽”锁比窄锁更容易推理,因此值得从宽锁开始并根据需要进行优化,但在某些情况下,我们从经验和熟悉度中学习到,窄锁更适合这种模式。

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

【讨论】:

我真的很喜欢你在这里的想法。这是一个很好的方式来看待它。我希望我能接受两个答案或 +5 这个答案,非常感谢 当需要考虑性能时,一个很重要的结果是可能同时命中的共享结构和命中的共享结构之间的区别.有时我们并不期望这种行为经常发生,但它可能会发生,所以我们需要锁定(锁定一次失败就可以毁掉一切)。其他时候,我们知道很多线程确实会同时访问相同的对象。然而其他时候,我们并不期望会有很多并发,但我们错了。当您需要提高性能时,那些具有大量并发性的优先。 在您的选择中,volatile 不是必需的,但它应该是 readonly。见***.com/q/12159698/428724。【参考方案4】:

原因是性能。如果instance != null(除了第一次之外总是如此),则无需执行昂贵的lock:同时访问初始化的单例的两个线程将不必要地同步。

【讨论】:

【参考方案5】:

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

这种模式称为双重检查锁定:http://en.wikipedia.org/wiki/Double-checked_locking

【讨论】:

【参考方案6】:

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;
            
        
    

【讨论】:

不是让实例变量易失,做同样的事情吗?【参考方案7】:

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

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;
        
    

【讨论】:

【参考方案8】:

您可以急切地创建一个线程安全的 Singleton 实例,具体取决于您的应用程序需要,这是简洁的代码,尽管我更喜欢 @andasa 的惰性版本。

public sealed class Singleton

    private static readonly Singleton instance = new Singleton();

    private Singleton()  

    public static Singleton Instance()
    
        return instance;
    

【讨论】:

【参考方案9】:

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

private static readonly Singleton singleInstance = new Singleton();

这里 CLR(公共语言运行时)将负责对象初始化和线程安全。这意味着我们不需要显式编写任何代码来处理多线程环境的线程安全。

“单例设计模式中的 Eager loading 不是一个过程 我们需要在这个时候初始化单例对象 应用程序启动而不是按需启动并将其保存在内存中 以后会用到。”

public sealed class Singleton
    
        private static int counter = 0;
        private Singleton()
        
            counter++;
            Console.WriteLine("Counter Value " + counter.ToString());
        
        private static readonly Singleton singleInstance = new Singleton(); 

        public static Singleton GetInstance
        
            get
            
                return singleInstance;
            
        
        public void PrintDetails(string message)
        
            Console.WriteLine(message);
        
    

来自主要:

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");
        

【讨论】:

不错的选择,但没有回答关于问题中提到的具体实现中的锁定检查的问题 不是直接使用,但可以用作替代“线程安全 C# 单例模式”。【参考方案10】:

抗反射单例模式:

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");
    

【讨论】:

以上是关于线程安全的 C# 单例模式的主要内容,如果未能解决你的问题,请参考以下文章

C# 两行代码实现 延迟加载的单例模式(线程安全)

单例模式再议

怎么实现一个线程安全的单例模式

单例模式的线程安全性?

Spring 单例 多例 线程安全等问题,想请教大家

LINUX多线程(线程池,单例模式,线程安全,读者写者模型)