线程安全C#单例模式


79

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

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

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

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

         return instance;
      }
   }

执锁昂贵吗?



懒惰的静态初始化将是更好的选择
Mitch Wheat

1
我在这里也得到其他带有解释的示例:csharpindepth.com/Articles/General/Singleton.aspx
Serge Voloshenko 2016年

对于Java世界,这里的问题完全相同。
RBT

Answers:


133

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

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

可以这样考虑:仅当答案为“是,该对象已经构造”时,才可以进行裸null核对(不带lock),以提供正确的可用答案。但是,如果答案是“尚未构建”,那么您将没有足够的信息,因为您真正想知道的是它“尚未构建,并且没有其他线程打算在不久后构建它”。因此,您将外部检查用作非常快速的初始测试,并且仅在答案为“否”的情况下,才启动正确的,无错误但“昂贵的”过程(锁定然后检查)。

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


1
感谢您提供有用的链接,内容丰富。非常感激。
韦恩·菲普斯

双重检查的锁定-链接不再起作用。
El Mac

对不起,我的意思是另一个。
El Mac

1
@ElMac:Skeet的网站关闭了ATM,它将在适当的时候备份。我会牢记这一点,并确保当链接出现时该链接仍然有效,谢谢。
2014年

3
从.NET 4.0开始,Lazy<T>这项工作就完美地完成了。
ilyabreev

34

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)或更高版本。


我得到“System.MissingMemberException:‘该延迟初始化类型不具有公共,无参数构造函数’”有了这个代码.Net的4.6.1 / C#6
ttugates

@ttugates,您说得对,谢谢。使用懒惰对象的值工厂回调更新的代码。
andasa

14

执行锁定:非常便宜(比空测试还昂贵)。

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

当另一个线程拥有锁并且其他数十个线程也在等待锁时执行锁定:崩溃。

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

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

(顺便说一句,如果您可以只使用private static volatile Singleton instance = new Singleton()或者不使用单例,而是使用静态类,则在这些方面都比较好)。


1
我真的很喜欢你的想法。这是查看它的好方法。我希望我可以接受两个答案,或者我可以接受+5,非常感谢
Wayne Phipps 2012年

2
后果之一,当它的时间来看看性能变得重要,是共享结构之间的差异可以被同时击中,那些。有时我们并不期望这种行为经常发生,但是确实有可能发生,因此我们需要锁定(锁定只需要一次失败就可以破坏一切)。其他时候,我们知道很多线程确实会同时击中相同的对象。但在其他时候,我们并不期望会有很多并发,但是我们错了。当您需要提高性能时,具有大量并发的优先。
乔恩·汉娜

您也可以选择,volatile但不是必须的readonly。请参阅stackoverflow.com/q/12159698/428724
wezten

7

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



3

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


是不是使实例变量具有可变性,是否也一样?
ΕГИІИО

1

您可以根据您的应用程序需求急于创建一个线程安全的Singleton实例,这是简洁的代码,尽管我希望使用@andasa的惰性版本。

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

    private Singleton() { }

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

0

这称为双重检查锁定机制,首先,我们将检查实例是否已创建。如果不是,那么我们只会同步方法并创建实例。它将大大提高应用程序的性能。执行锁定很重。因此,为了首先避免锁定,我们需要检查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;
        }
    }
}

0

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

private static readonly Singleton singleInstance = new Singleton();

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

“急于加载单例设计模式并不是一个过程,在该过程中,我们不需要在应用程序启动时初始化单例对象,而无需按需初始化,并将其保留在内存中以备将来使用。”

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

不错的选择,但没有回答问题中提到的特定实现中的锁定检查问题
Wayne Phipps

不是直接使用,但可以用作替代的“线程安全C#单例模式”。
Jaydeep Shil,

0

耐反射的Singleton模式:

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");
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.