public sealed
class Singleton
{
static readonly Singleton instance=new
Singleton();
//设置静态构造器将告诉编译器
//
将此类型的BeforeFieldInit设为未标记
//这样就可以实现惰性加载了
static Singleton()
{
}
Singleton()
{
}
public static Singleton Instance
{
get
{
return instance;
}
}
}
版本五的实现也比较简单。因为我们设置了一个静态构造器,因此我们可以知道当且仅当该类型被初始化时或是类型的成员第
一次被访问或调用时instance会实例化,这也就保证了惰性加载。但这也存在着一些问题:
我们希望在第一次访问Instance属性的时候instance被初始化。事实上,如果Singleton类还存在这其他的公有字段或方法,
在他们第一次被访问或调用时,instance就已经被初始化了。因此,这个版本并没有完整的实现惰性加载。
让我们期待最后一个版本吧:
版本六:线程安全,完全惰性加载
public sealed
class Singleton
{
Singleton()
{
}
public static Singleton Instance
{
get
{
return Nested.instance;
}
}
//嵌套类不可在外部被初始化
class Nested
{
// Explicit static constructor to tell C#
compiler
// not to mark type as
beforefieldinit
static Nested()
{
}
internal static readonly Singleton instance =
new Singleton();
}
}
你可以好好体会以下这个版本,虽然表面上有些复杂,但其实它才是最完美的版本
这个版本兼顾了线程安全、效率和惰性加载等多项因素
它其中暗藏了许多玄机:
在这个版本中,初始化在第一次引用嵌套类的静态成员时发生。因此我们可以肯定初始化将在第一次访问Singleton
的Instance属性时发生。因此我们也就实现了完全的惰性加载,但它也有着与第五个版本相当的高效率。另一点需要
注意的是,虽然嵌套类可以访问包含它的类型的私有成员,
但反之包含类不能访问嵌套类的私有成员,因此我们需要将instance的可访问性设为internal。这样并不会造成任何问
题,因为这个嵌套类是私有的,因此它的这个internal成员并不能被除开Singleton的外界访问。
好,我们已经看完了以上的六个版本,下面我们来进行一些总结:
效率与惰性加载的考量
事实上,理论和实践总是存在着很大的差别的。有时候,虽然我们已经拥有了理论上最完美的解决方案,但我们
在实践中却并不一定需要这样完美的解决方案。因为一般的解决方案就可以完全满足我们的需求了。
这就是我下面想讨论的问题:
在单例模式的实际应用中,我们是否真的需要惰性加载呢?
在单例模式的各个实现版本中,效率到底会有多大的差别呢?
1.惰性加载
我们真的需要惰性加载吗?为什么不直接用版本四呢?
就个人而言,我觉得版本四完全能够应付一般的应用场景了。版本四从实现上来说比较简单,当我们在单例的第一次
(也是唯一一次)初始化时不会造成大量的系统开销时,我们就可以考虑用这种非惰性加载的简单实现了。如果单例的初
始化真的会造成大量的系统开销时,那么我们就需要应用其他的版本了,以此来保证单例是惰性加载的。
2.性能
在单例模式的各个实现版本中,效率肯定也存在着一定的差别,现在我们就来对各个版本的效率进行一个具体的测试吧:
我们的思路是这样的,对每一个单例的实现版本进行10亿次访问(即连续调用Singleton.Instance)十亿次,然后比较各
个版本所花费的时间。
最后再进行具体的分析。以下是具体的测试代码
public class
SingletonBenchmark { const int Iterations =
1000000000;
static
DateTime timer;
static
void Main() { ResetTimer(null); for
(int i = 0; i < Iterations; i++) {
SimpleNotThreadSafe sl = SimpleNotThreadSafe.Instance;
} ResetTimer("SimpleNotThreadSafe");
for
(int i = 0; i < Iterations; i++) {
SimpleLock sl = SimpleLock.Instance; }
ResetTimer("SimpleLock");
for
(int i = 0; i < Iterations; i++) { FixedDcl
dcl = FixedDcl.Instance; }
ResetTimer("FixedDcl");
for
(int i = 0; i < Iterations; i++) {
BrokenDcl dcl = BrokenDcl.Instance; }
ResetTimer("BrokenDcl");
for
(int i = 0; i < Iterations; i++) {
SimpleNoLockLazy inst = SimpleNoLockLazy.Instance;
} ResetTimer("SimpleNoLockLazy");
for
(int i = 0; i < Iterations; i++) {
SimpleNoLockNotAsLazy inst = SimpleNoLockNotAsLazy.Instance;
} ResetTimer("SimpleNoLockNotAsLazy");
for
(int i = 0; i < Iterations; i++) {
NestedLazy nl = NestedLazy.Instance; }
ResetTimer("NestedLazy");
for
(int i = 0; i < Iterations; i++) {
NestedNotAsLazy nl = NestedNotAsLazy.Instance; }
ResetTimer("NestedNotAsLazy"); Console.Read();
}
static
void ResetTimer(string description) { DateTime now =
DateTime.UtcNow; if (description != null)
{ Console.WriteLine("{0} took {1}", description, now -
timer); } timer = DateTime.UtcNow;
} }
public
sealed class SimpleLock { static SimpleLock instance =
null; static readonly object padlock = new object();
SimpleLock() { }
public
static SimpleLock Instance { get
{ lock (padlock) {
if (instance == null) {
instance = new SimpleLock(); }
return instance; } } }
}
public
sealed class SimpleNotThreadSafe { static SimpleNotThreadSafe
instance = null;
SimpleNotThreadSafe() { }
public
static SimpleNotThreadSafe Instance {
get { if (instance == null)
{ instance = new
SimpleNotThreadSafe(); } return
instance; } } }
public
sealed class BrokenDcl { static BrokenDcl instance =
null; static readonly object padlock = new object();
BrokenDcl() { }
public
static BrokenDcl Instance { get
{ if (instance == null)
{ lock (padlock)
{ if (instance == null)
{ instance = new
BrokenDcl(); }
} } return instance;
} } }
public
sealed class FixedDcl { static volatile FixedDcl instance =
null; static readonly object padlock = new object();
FixedDcl() { }
public
static FixedDcl Instance { get
{ if (instance == null)
{ lock (padlock)
{ if (instance == null)
{ instance = new
FixedDcl(); }
} } return instance;
} } }
public
sealed class SimpleNoLockLazy { static readonly
SimpleNoLockLazy instance = new SimpleNoLockLazy();
//
Explicit static constructor to tell C# compiler // not to mark type
as beforefieldinit static SimpleNoLockLazy() {
}
SimpleNoLockLazy() { }
public
static SimpleNoLockLazy Instance { get
{ return instance; } }
}
public
sealed class SimpleNoLockNotAsLazy { static readonly
SimpleNoLockNotAsLazy instance = new SimpleNoLockNotAsLazy();
SimpleNoLockNotAsLazy() { }
public
static SimpleNoLockNotAsLazy Instance {
get { return instance;
} } }
public
sealed class NestedLazy { NestedLazy() {
}
public
static NestedLazy Instance { get
{ return Nested.instance; }
}
class
Nested { // Explicit static constructor to tell C#
compiler // not to mark type as beforefieldinit
static Nested() { }
internal static readonly NestedLazy instance = new NestedLazy();
} }
public
sealed class NestedNotAsLazy { NestedNotAsLazy()
{ }
public
static NestedNotAsLazy Instance { get
{ return Nested.instance; }
}
class
Nested { internal static readonly NestedNotAsLazy
instance = new NestedNotAsLazy(); } }
下面来看看测试的输出结果 SimpleNotThreadSafe took
00:00:12.8448320 //版本1,非线程安全的实现,效率也不是最高,我们肯定不会使用这个版本 SimpleLock took
00:00:40.6646240
//版本2,简单锁,线程安全,惰性加载,但效率是最低的 FixedDcl took
00:00:15.3112960
//版本3的改进版,线程安全,惰性加载,效率还行 BrokenDcl took
00:00:15.9038880
//版本3的原始版,惰性加载,但不是完全线程安全的,这个版本我们也不会用 SimpleNoLockLazy took
00:00:09.9459360 //版本5,线程安全,效率也是最高的,但并不是完全惰性的(见前面版本5的讨论) SimpleNoLockNotAsLazy took
00:00:10.6346240 //版本4,实现简单,线程安全,效率较高,非惰性加载 NestedLazy took
00:00:14.1261120
//版本6,兼顾了线程安全、效率和惰性加载,就是实现有点复杂 NestedNotAsLazy took
00:00:10.0260160 //版本6的非惰性加载版,线程安全,效率也高,但感觉用它还不如用版本4
综合以上的评述,我们可以知道我们真正可能用到的是2、3的改进版、4、5、6五个版本。
由于我们对每一个单例都进行了10亿此的读取访问,除了简单锁外,其他的各个实现版本花费的时间都在十几秒左右(实际上还要减去
调用ResetTimer的时间)。既然10亿次的访问才会花费十几秒,除非我们真的有大量的并发处理的要求,否则性能并不是我们需要首要
关心的问题。因此简单锁的四十秒也是完全可以接受的。
总结:
现在我们基本上可以得出结论了,在一般的应用中,如果单体的初始化并不会造成大量的系统开销(也就是说惰性加载与否并不重要)
那想都别想,用版本4吧,这才是最简单的实现。如果你真的需要惰性加载而对性能又没有太高要求时,那就用版本2吧。如果你是一个
完美主义者而且又能接受版本6的复杂度,那它才是最适合于你的,因为它在各个场景下都能适用。就个人而言,我不会用双险锁的版本,
如果你觉得能完全把握理解双险锁的话,那版本3的改进版也是可以接受的。另外,如果你的单例实现中除了Instace属性外没有其他的静态方法
和字段,那版本5也是一个很好的选择,毕竟它的效率是最高的 |