问题描述
我有一些有关此处记录的单身模式的问题: http://msdn.microsoft.com/en-en-us/en-us/library/library/fff650316.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; } }
执行锁是否昂贵?
推荐答案
与简单的指针检查instance != null.
您在此看到的图案称为双检查锁定.它的目的是避免仅需要一次只需要一次(首次访问单例时)的昂贵锁操作.实现之所以如此,是因为它还必须确保在初始化单例时不会引起线程竞赛条件引起的错误.
以这种方式考虑:裸露的null检查(无lock)可以保证只有在答案为"是的,对象已经构造"时,才能为您提供正确的可用答案.但是,如果答案是"尚未构造的",那么您没有足够的信息,因为您真正想知道的是"尚未构建,也没有其他线程打算尽快构建它" .因此,您将外部检查用作非常快速的初始测试,并启动适当的,无错误但"昂贵"的过程(锁定然后检查),仅当答案为"否".
问题描述
I have some questions regarding the the singleton pattern as documented here: http://msdn.microsoft.com/en-us/library/ff650316.aspx
The following code is an extract from the article:
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; } } }
Specifically, in the above example, is there a need to compare instance to null twice, before and after the lock? Is this necessary? Why not perform the lock first and make the comparison?
Is there a problem in simplifying to the following?
public static Singleton Instance { get { lock (syncRoot) { if (instance == null) instance = new Singleton(); } return instance; } }
Is the performing the lock expensive?
推荐答案
Performing the lock is terribly expensive when compared to the simple pointer check instance != null.
The pattern you see here is called double-checked locking. Its purpose is to avoid the expensive lock operation which is only going to be needed once (when the singleton is first accessed). The implementation is such because it also has to ensure that when the singleton is initialized there will be no bugs resulting from thread race conditions.
Think of it this way: a bare null check (without a lock) is guaranteed to give you a correct usable answer only when that answer is "yes, the object is already constructed". But if the answer is "not constructed yet" then you don't have enough information because what you really wanted to know is that it's "not constructed yet and no other thread is intending to construct it shortly". So you use the outer check as a very quick initial test and you initiate the proper, bug-free but "expensive" procedure (lock then check) only if the answer is "no".
The above implementation is good enough for most cases, but at this point it's a good idea to go and read Jon Skeet's article on singletons in C# which also evaluates other alternatives.
其他推荐答案
The Lazy<T> version:
public sealed class Singleton { private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton()); public static Singleton Instance => lazy.Value; private Singleton() { } }
Requires .NET 4 and C# 6.0 (VS2015) or newer.
其他推荐答案
Performing a lock: Quite cheap (still more expensive than a null test).
Performing a lock when another thread has it: You get the cost of whatever they've still to do while locking, added to your own time.
Performing a lock when another thread has it, and dozens of other threads are also waiting on it: Crippling.
For performance reasons, you always want to have locks that another thread wants, for the shortest period of time at all possible.
Of course it's easier to reason about "broad" locks than narrow, so it's worth starting with them broad and optimising as needed, but there are some cases that we learn from experience and familiarity where a narrower fits the pattern.
(Incidentally, if you can possibly just use private static volatile Singleton instance = new Singleton() or if you can possibly just not use singletons but use a static class instead, both are better in regards to these concerns).