分享

单例模式深入研究(二):具体实现 续

 zop个人图书馆 2012-11-16

续上一篇

版本五:不用lock实现线程安全,但惰性加载稍有问题

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

综合以上的评述,我们可以知道我们真正可能用到的是23的改进版、456五个版本。

由于我们对每一个单例都进行了10亿此的读取访问,除了简单锁外,其他的各个实现版本花费的时间都在十几秒左右(实际上还要减去

调用ResetTimer的时间)。既然10亿次的访问才会花费十几秒,除非我们真的有大量的并发处理的要求,否则性能并不是我们需要首要

关心的问题。因此简单锁的四十秒也是完全可以接受的。

总结:

现在我们基本上可以得出结论了,在一般的应用中,如果单体的初始化并不会造成大量的系统开销(也就是说惰性加载与否并不重要)

那想都别想,用版本4吧,这才是最简单的实现。如果你真的需要惰性加载而对性能又没有太高要求时,那就用版本2吧。如果你是一个

完美主义者而且又能接受版本6的复杂度,那它才是最适合于你的,因为它在各个场景下都能适用。就个人而言,我不会用双险锁的版本,

如果你觉得能完全把握理解双险锁的话,那版本3的改进版也是可以接受的。另外,如果你的单例实现中除了Instace属性外没有其他的静态方法

和字段,那版本5也是一个很好的选择,毕竟它的效率是最高的

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多