线程安全的C#单例模式
我有关于单例模式的一些问题在这里logging: 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; } } }
具体来说,在上面的例子中,是否需要将实例与空值比较两次,在锁之前和之后? 这是必要的吗? 为什么不先执行locking并进行比较?
简化到以下是否有问题?
public static Singleton Instance { get { lock (syncRoot) { if (instance == null) instance = new Singleton(); } return instance; } }
表演的锁是否昂贵?
与简单指针检查instance != null
相比,执行locking非常昂贵。
你在这里看到的模式称为双重检查locking 。 它的目的是为了避免只需要一次的昂贵的锁操作(当第一次访问单例时)。 实现是这样的,因为它也必须确保当单例初始化时不会有线程争用条件产生的错误。
这样想一下:只有当答案是“是的,对象已经构build”时,一个null
检查(没有lock
)才能保证给你一个正确的可用答案。 但是,如果答案是“还没有构build”,那么你没有足够的信息,因为你真正想知道的是,它“还没有构build,没有其他线程打算立即构build ”。 所以你使用外部检查作为一个非常快速的初始testing,并且只有当答案是“否”时,才启动正确的,无缺陷但“昂贵”的过程(locking然后检查)。
上面的实现对于大多数情况来说已经足够了,但是在这一点上,阅读Jon Skeet关于C#中单例的文章是一个好主意。
执行一个锁:相当便宜(比空testing还要贵)。
当另一个线程拥有它时执行一次locking:在locking的时候,你将得到任何他们仍然要做的事情的代价,并加到你自己的时间。
当另一个线程拥有它时,执行一个锁,还有许多其他的线程也在等待:Crippling。
出于性能方面的原因,您总是希望尽可能在最短的时间内拥有另一个线程想要的locking。
当然,关于“广泛”locking的推理比狭窄的推理更容易,因此,从广泛的angular度和根据需要进行优化开始,值得开始。但是,我们也可以从经验和熟悉中学习一些适合模式的方法。
(顺便说一句,如果你可能只是使用private static volatile Singleton instance = new Singleton()
或者如果你可能只是不使用单例,而是使用静态类,而这两个问题都更好)。
原因是性能。 如果instance != null
(除了第一次以外总会是这种情况),所以不需要进行昂贵的lock
:同时访问初始化单例的两个线程将不必要地同步。
懒惰的版本:
public sealed class Singleton { private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(); private Singleton() { } public static Singleton Instance => lazy.Value; }
需要.NET 4和C#6.0(VS2015)或更新。
在几乎所有情况下(即:除了第一个以外的所有情况), instance
不会为空。 获取一个锁比一个简单的检查代价更高,因此在locking之前检查instance
的值是一个很好的自由优化。
这种模式称为双重检查locking: http : //en.wikipedia.org/wiki/Double-checked_locking
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; } } }