参考:
- 《Implementing the Singleton Pattern in C#》
- 《CLR via C# (第4版)》
方式1. 非线程安全
public class Singleton
{
private static Singleton instance = null;
private Singleton() { }
public static Singleton GetInstance()
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
可能有两个线程都执行了 'if (instance==null)',且得到的结果都是 true,然后两个线程各自创建一个实例。
(有可能在执行 'if (instance==null)' 前,实例就已经创建好了,但是 memory model 不能保证其它线程能及时发现 instance 的值已经改变。(内存栅栏,memory barrier))
方式2. 简单的线程安全
public sealed class Singleton
{
private static readonly object s_lock = new object();
private static Singleton instance = null;
private Singleton() { }
public static Singleton GetInstance()
{
lock (s_lock)
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
}
}
lock 可以消除内存栅栏的影响。
在CLR 中,任何锁方法的调用都构成了一个完整的内存栅栏,在栅栏之前写入的任何变量都必须在栅栏之前完成;在栅栏之后的任何变量读取都必须在栅栏之后开始。
缺点:每次获取 instance 都得拿锁,性能会降低。
方式3. 用双检锁实现线程安全
public class Singleton
{
private static object s_lock = new object();
private static Singleton instance = null;
private Singleton() { }
public Singleton GetInstance()
{
if (instance == null)
{
Monitor.Enter(s_lock);
if (instance == null)
{
Singleton temp = new Singleton();
Volatile.Write(ref instance, temp);
}
}
return instance;
}
}
> 为什么用 Volatile.Write 而不是 instance = new Singleton() ?
如果用 instance = new Singleton(),编译器可能先为 Singleton 分配内存,将引用赋给 instance,再调用构造器。这样可能在调用构造器完成之前,另一个线程调用了 GetInstance 并使用这个未构造完成的 Singleton 对象。
Volatile.Write 保证 temp 中的引用只有在构造器结束后才赋给 instance。
方式4. 不用锁的线程安全、非延迟创建
public class Singleton
{
private static readonly Singleton instance = new Singleton();
private Singleton() { }
public static Singleton GetInstance()
{
return instance;
}
}
缺点:首次访问类的任何成员都会调用类型构造器,从而创建实例。
方式5. 延迟创建
public class Singleton
{
private Singleton() { }
public static Singleton GetInstance()
{
return Nested.instance;
}
private class Nested
{
internal static readonly Singleton instance = new Singleton();
}
}
方式6. 用 Lazy<T>
public class Singleton
{
private static Lazy<Singleton> instance
= new Lazy<Singleton>(() => new Singleton(), true);
private Singleton() { }
public Singleton GetInstance()
{
return instance;
}
}
小结:
不要炫技!
延迟创建实例确实可以加快类的加载速度,但这只是转移了耗时的阶段。是快速启动更重要,还是后续快速获取实例更重要,这得看具体业务需求。而且实现延迟创建的代码一般都更复杂。
一般我用这种方式:
public class Singleton
{
private static Singleton instance = new Singleton();
private Singleton() { }
public static Singleton GetInstance()
{
return instance;
}
}
如果需要延迟创建就用方式6。
517

被折叠的 条评论
为什么被折叠?



