Singleton Best Practice

  Singleton,单例模式,是指只产生一个实例来访问。注意看注释,下面方法是AppDomain内的单例,当然,大多数程序都是在一个AppDomain中运行。

  比如下面代码就是简单的单例:

///<summary>
/// Singleton instance per AppDomain, not thread safe
///</summary>
///<typeparam name="T"></typeparam>
public staticclass Singleton<T>where T : class, new()
{
privatestatic T _instance;

publicstatic T Instance
{
get
{
if (_instance ==default(T))
_instance
=new T();
return _instance;
}
}
}

  但是它不是线程安全的,2个线程过来,很有可能创建2个实例,于是如果我们保证线程安全,还得加lock:

///<summary>
/// Singleton instance per AppDomain
///</summary>
///<typeparam name="T"></typeparam>
publicstaticclass Singleton<T>where T : class, new()
{
privatestatic T _instance;
privatestaticreadonlyobject _padLock =newobject();

publicstatic T Instance
{
get
{
lock (_padLock)
{
//only one thread can enter this scrope each time
if (_instance ==default(T))
_instance
=new T();
return _instance;
}
}
}
}

  虽然它已经线程安全,但是由于lock块,及时_instance != null,每次也要lock,多线程场景下性能差,所以可以采用Double Check:

///<summary>
/// Singleton instance per AppDomain
///</summary>
///<typeparam name="T"></typeparam>
publicstaticclass Singleton<T>where T : class, new()
{
privatestatic T _instance;
privatestaticreadonlyobject _padLock =newobject();

publicstatic T Instance
{
get
{
//if _instance!= default(T), it does not lock, just return
if (_instance==default(T))
{
lock (_padLock)
{
if (_instance ==default(T))
_instance
=new T();
}
}
return _instance;
}
}
}

  Double check在绝大多数情况下工作的很好,但是在多核心情况下会出现创建多个实例的情况。

  比如双核,CPU0执行到释放锁得时候,由于CPU高速缓存(cache)和写缓存(Store Buffer)的存在,主内存中可能_instance对象还没有创建,当CPU1执行到_instance==null时候,判断为true,然后接下去就创建了另外一个对象,因此,double check给出了2个完全不同的对象。

  根据 Memory Reordering/Memory Model 及其对.NET的影响 这篇文章来看,在多处理器环境下,可以这样解决问题:

///<summary>
/// Singleton instance per AppDomain
///</summary>
///<typeparam name="T"></typeparam>
publicstaticclass Singleton<T>where T : class, new()
{
//add volatile to make sure _instance exist in memory, not cpu cache
privatevolatilestatic T _instance;
privatestaticreadonlyobject _padLock =newobject();

publicstatic T Instance
{
get
{
//if _instance!= default(T), it does not lock, just return
if (_instance==default(T))
{
lock (_padLock)
{
if (_instance ==default(T))
_instance
=new T();
}
}
return _instance;
}
}
}

  volitile关键字将让编译器认为是多线程代码,从而不会执行优化;同时保持内存中字段总是最新的,CPU缓存将不起作用。

  但是,加入volatile没有考虑到写缓存同步到主内存中,这样CPU0 new T()后,写入CPU0的cache中,而此时CPU1访问主存中的_instance,发现为null,于是它也new T(),这仅在IA64模式下可能发生,因为IA64目前是弱内存模型。而且加入volatile会带来性能开销(不用cpu cache),每次访问都访问内存,而且并发专家一再告诫我们:

     .net里为什么尽量避免Volatile关键字

  "Loads are not reorderd with other loads" is a FACT!!

  因此,我们可以使用Thread.MemoryBarrier()内存栅栏来保证CPU不进行乱序处理,解决Cache一致性。

  去掉了volatile关键字,一旦_instance初始化好,之后访问都可以利用Cpu cache。

///<summary>
/// Singleton instance per AppDomain
///</summary>
///<typeparam name="T"></typeparam>
publicstaticclass Singleton<T>where T : class, new()
{
privatestatic T _instance;
privatestaticreadonlyobject _padLock =newobject();

publicstatic T Instance
{
get
{
//if _instance!= default(T), it does not lock, just return
if (_instance==default(T))
{
lock (_padLock)
{
if (_instance ==default(T))
{
var temp
=new T();
//The processor executing the current thread cannot reorder instructions in such a way that
//memory accesses prior to the call to MemoryBarrier execute after memory accesses that
//follow the call to MemoryBarrier.
Thread.MemoryBarrier();
_instance
= temp;
}
}
}
return _instance;
}
}
}

  如果我们要进程内单例,可以使用Mutex来保证,同样如果是多进程内单例,可以使用命名Mutex:

///<summary>
/// Singleton instance per AppDomain
///</summary>
///<typeparam name="T"></typeparam>
publicstaticclass Singleton<T>where T : class, new()
{
privatestatic T _instance;
privatestaticreadonly Mutex _mutex =new Mutex();

publicstatic T Instance
{
get
{
//if _instance != default(T), it does not lock, just return
if (_instance ==default(T))
{
_mutex.WaitOne();
if (_instance ==default(T))
{
var temp
=new T();
//The processor executing the current thread cannot reorder instructions in such a way that
//memory accesses prior to the call to MemoryBarrier execute after memory accesses that
//follow the call to MemoryBarrier.
Thread.MemoryBarrier();
_instance
= temp;
}
_mutex.ReleaseMutex();
}
return _instance;
}
}
}

  .net本身对静态初始化是单线程访问的,因此我们可以利用这个特性,写成如下代码:  

///<summary>
/// Singleton instance per AppDomain
///</summary>
///<typeparam name="T"></typeparam>
public staticclass Singleton<T>where T : class, new()
{
///<summary>
/// Lazy load singleton instance
///</summary>
publicstatic T Instance
{
get
{
return Nested.instance;
}
}

privateclass Nested
{
///<summary>
/// .net make sure that during static constructor, it's thread safe per appdomain.
///</summary>
internalstaticreadonly T instance =new T();
}
}

当然,泛型约束为new()的话,意味着T必须有public constructor,如果没有呢?比如是private constructor怎么办?

其实一般来讲,人家用private constructor,就是为了不让你new 出来一个实例,如果你非得要搞出来,可以采用以下方法:

///<summary>
/// Singleton instance per AppDomain
///</summary>
///<typeparam name="T"></typeparam>
public staticclass Singleton<T> where T : class
{
///<summary>
/// Lazy load singleton instance
///</summary>
publicstatic T Instance
{
get
{
return Nested.instance;
}
}

privateclass Nested
{
///<summary>
/// .net make sure that during static constructor, it's thread safe per appdomain.
///</summary>
internalstaticreadonly T instance = (T)Activator.CreateInstance(typeof(T), true);
}
}

自从有了.net4.0的Lazy<T>后,还可以这样搞:

///<summary>
/// Singleton instance per AppDomain
///</summary>
///<typeparam name="T"></typeparam>
public staticclass Singleton<T>where T : class, new()
{
///<summary>
/// static readonly make sure _lazy is thead safe and singleton.
/// using Lazy<T> make sure that it's lazy value creation is thead safe.
///</summary>
privatestaticreadonly Lazy<T> _lazy =new Lazy<T>(() =>new T(), true);

publicstatic T Instance
{
get
{
return _lazy.Value;
}
}
}

如此这般,就得到了线程安全的Singleton实例。当然我们还可以继续优化,比如有参数的构造函数,加入WeakReference等等…… 

posted @ 2011-05-26 23:35  primeli  阅读(482)  评论(0编辑  收藏  举报