怪怪 | Nothing, Everything
"有过一个发疯的时刻,有感觉的钢琴以为它是世界上仅有的一架钢琴,宇宙的全部和谐都发生在它身上." - 狄德罗
随笔 - 67, 文章 - 2, 评论 - 1337, 引用 - 15
数据加载中……
对Singleton的实现方法做一个总结
原帖:
http://www.cnblogs.com/ASPNET2008/archive/2008/05/09/1190328.html
首先直接文章作者及回复中的兄弟提到的两种Singleton方法。
第一种
public
class
Singleton
{
private
static
Singleton _instance
=
null
;
private
static
readonly
object
lockHelper
=
new
object
();
private
Singleton()
{
}
public
static
Singleton CreateInstance()
{
//
这样lock以及lock块内的代码只会在第一次调用CreateInstance方法的时候执行,
//
第一次调用该方法后_instance就不再为null了,if块内的代码就无须执行了
if
(_instance
==
null
)
{
lock
(lockHelper)
{
if
(_instance
==
null
)
_instance
=
new
Singleton();
}
}
return
_instance;
}
}
第二种
public
class
Singleton
{
private
static
readonly
Singleton _instance
=
new
Singleton();
static
Singleton()
{ }
private
Singleton()
{ }
public
static
Singleton CreateInstance()
{
return
_instance; }
}
先说说以上两种方法。
第一种方法, 显而易见的, 性能不佳; 而且最好加上一句
Thread
.MemoryBarrier()。 这是为了线性化内存存取, 避免处理器调整指令顺序导致问题, 至于到底怎么个坏掉法, 我就糊里糊涂了。 但至少从这个角度看, 其实第一种方法, 虽然我们最常用, 如果不加这么一句, 可能反而是不安全的。
第二种方法, 有兄弟说它不是线程安全的, 事实上它是线程安全的, 但不一定是AppDomain安全的。 在同一个AppDomain内, 初始化仅进行一次。但是这种方法有一个细节。 对于没有静态构造函数的类, .NET会加上一个
BeforeFieldInit的标志,它的MSDN解释是:
Specifies that calling static methods of the type does not force the system to initialize the type. 这就是说, 假设Singleton存在一个静态方法, 比如Singleton.Do(), 这个Do被执行了, 但是_instance并不一定被初始化(当然也不一定不被),只要没有其它条件触发(在后面我们会想一个办法, 保证一定得到这个LazyLoad的效果); 也就是说, 初始化的时间是不固定的; 而且还有别的可能: 比如在程序集被加载, 在该类被某种方式访问, 甚至一个很随机的时刻。 传说中初始化仅仅保证在该字段被访问之前, 由运行时决定, 到底咋回事就不是我关心的范畴了。
其实早晚本来也是无所谓的, 关键是注意到这一点, 可以避免并没有触发初始化, 可是流程上人脑中有一个预期的行为,进行编程时所造成的不确定性(尽管很难想象能够掉进这个陷阱的场景,估计得特别特殊的应用, 比如你脑海里以为初始化了, 就向其它东西发一个信号, 而且会造成问题等等)。
如果存在静态构造函数, 那么当这个类的任意成员被访问前, 会进行初始化。 微软这么设计显然是有道理的: 既然写了静态构造器, 那么编程者很显然希望它最早执行, 同时也就会捎带上初始化。 但这种方式会导致, 如果咱们访问Singleton.Do(), 那么所有的字段都会被初始化。也许我们调用Do()的时候因为用不着这个实例, 而且这个实例会耗费很多资源,于是就想尽量Lazy Load用不着就不new, 这样要么使用第一种方法, 或者引出了第三个方法。
public
class
Singleton
{
private
Singleton()
{ }
public
static
Singleton CreateInstance()
{
return
Holder._instance; }
private
class
Holder
{
static
Holder()
{
}
public
static
readonly
Singleton _instance
=
new
Singleton();
}
}
用内部私有的类Holder玩一个花招, 实际上这是一种最好的办法: 当Holder被访问时, new Singleton(), 而Do()的时候, 则不会这么做。这样, 它既有第一种方法LazyLoad的特性, 又具有第二种方法的直接性和性能, 还避免了乱七八糟的很难搞明白的问题。
回头再说说关于第一种方法的另一个替代品, 这样:
public
class
Singleton
{
public
static
Singleton _instance
=
null
;
private
Singleton()
{
}
public
static
Singleton CreateInstance()
{
Interlocked.CompareExchange
<
Singleton
>
(
ref
_instance,
new
Singleton(),
null
);
return
_instance;
}
}
因为
Interlocked.CompareExchange
是一个原子操作, 所以是线程安全的。不过注意那个new Singleton(), 它会不断的被执行。 如果构造的过程很耗费资源, 这是一个问题。 其实利用delegate, 我们可以有一些新玩法, 把包括第二次及以后进入CreateInstance的开销降到最低; 如果谁想到了可以给我留言。(Update: 我的做法放在了第20楼, 注意脑袋回复中提到的问题, 其实这种做法我一般用在别处完成更贴切的任务, 具体分析日后再说了)
最后, 稍加改动, 可以把Singleton成泛型的。不过个人感觉意义不大了, 除非用Singleton的地方太多, 懒得打代码, 也可以考虑。 嗯, 结束了么? 不知道大家觉得这些是不是也一塌糊涂,自己感觉我不太适合写这类文章....
posted on 2008-05-10 02:02
怪怪
阅读(2190)
评论(27)
编辑
收藏
评论
#1楼
回复
引用
查看
好像又修改了一次,什么时候改的呀。
2008-05-10 06:37 |
金色海洋(jyk)
#2楼
回复
引用
查看
老怪物 后半夜您不睡觉啊 ^_^ 害的我一大早起来就品尝您的文章。
文中提到的第三种方法 是您自己想到滴啊?
2008-05-10 09:17 |
戏水
#3楼
回复
引用
查看
受小崔的影响,喜欢上这种讨论的风格了。。
2008-05-10 10:05 |
任力
#4楼
回复
引用
查看
这三种方法都见过,平时使用第二种,第三种很少使用。
不过使用时需要注意:既然已经创建了 Singleton.Instance 了,Do() 就没有必要再设置为 static 吧。所以使用时直接这样使用 Singleton.Instance.Do() 就可以避免初始化不确定的因素了;
第三种解决的问题是: 有了 Singleton.Instance 同时也存在 Singleton.Do(),使用 Singleton.Do() 时不能保证 mIntance 一定被初始化的问题,这样你也许就不能使用 Singleton 的成员变量。
不过看完博主比较的方法,深入地研究,是很值得学习的。辛苦了!
2008-05-10 10:21 |
alonesword [未注册用户]
#5楼
回复
引用
查看
第三种方法是通过什么确保线程安全的?
2008-05-10 10:23 |
戏水
#6楼
[
楼主
]
回复
引用
查看
@戏水
我当然没这么聪明, 印象里是从一英文站看来的, 人家玩得比我细多啦。 本来想直接转的, 一时找不到, 又考虑到语言问题, 就凭自己的使用经验重新写了。
第三种方法其实就是在类的内部玩一次带静态构造函数的第二种用法, 所以是安全的。
@alonesword
你说的对, 第二种方法一般来说足够用啦。
2008-05-10 10:35 |
怪怪
#7楼
回复
引用
查看
先学习
2008-05-10 11:41 |
侯垒
#8楼
回复
引用
查看
呵呵,Jon的《Implementing the singleton pattern》也是讲的这个,很细致。
http://www.yoda.arachsys.com/csharp/singleton.html
2008-05-10 12:33 |
Klesh Wong
#9楼
[
楼主
]
回复
引用
查看
@Klesh Wong
太谢啦, 比我印象里的那篇还好, 收了 :)
2008-05-10 12:41 |
怪怪
#10楼
回复
引用
查看
有时候还会用到线程级别或CallContext级别的Singleton。
2008-05-10 13:15 |
Jeffrey Zhao
#11楼
回复
引用
查看
Jeffrey Richter在他的《CLR via C#》有一节专门讲解类型构造器。而他的试验表明BeforeFieldInit元数据Flag对性能有不小的影响,加上标记比不加标记性能高出一截。So这里,我觉得不应该加空的类型构造器来抑制编译器生成BeforeFieldInit元数据Flag。当然,这里也不大可能成为性能的瓶颈,呵呵。
我主要想指出的是怪怪老大给出的采用原子操作的替代方法是不正确的,:(
所有的CAS操作必须要在一个循环内使用。虽然大多数时候,CAS操作在第一次就比较替换成功,但不保证它肯定在第一次就成功。此外,此处是否存在ABA问题也未可知,需要进行严格测试其正确性,才能使用CAS原语。详情可以参考:
并发数据结构:迷人的原子
2008-05-10 13:31 |
Angel Lucifer
#12楼
回复
引用
查看
我从来都是用第三种的
2008-05-10 14:06 |
装配脑袋
#13楼
回复
引用
查看
@Angel Lucifer
LZ第四种方法中,即使CompareExchange失败,也意味着其他线程将实例更新到_instance了,所以不用重试了。尽管还是有构造函数不断执行的问题……
2008-05-10 14:28 |
装配脑袋
#14楼
[
楼主
]
回复
引用
查看
@Angel Lucifer
我的看法同脑袋, 不过我的知识比较粗糙, 还要多补补课。可别叫我“老大”, 但也别叫我“老大.Next()”就行... 看你的博客对并发很有兴趣, 以后我可经常去你那看有没有新东西 :P
其实我知道Interlocked的过程是这样: 反编译.NET的DLL看它怎么用的, 我就暂时认为它的用法没问题然后克隆之; 存在疑惑的地方注意一下, 然后在今后有意识的去注意相关的知识。
缺点是在碰到补习的机会以前,有点囫囵吞枣, 还是得大家多提醒 :)
@装配脑袋
你就不用说啦, 肯定什么都找好的用 :) 你觉得我说的用delegate解决效率问题的方式可行不?
2008-05-10 17:09 |
怪怪
#15楼
回复
引用
查看
Mark! 如何确保所有线程当中都是唯一? 那种方式较好?
2008-05-10 17:11 |
簡簡單單..
#16楼
回复
引用
查看
@怪怪
我都不知道你说的delegate做法是什么……
2008-05-10 19:31 |
装配脑袋
#17楼
回复
引用
查看
怪怪 把
http://www.yoda.arachsys.com/csharp/singleton.html
这个文章 翻译一下吧 。造福后世啊 。哈哈
2008-05-10 19:46 |
sitwo [未注册用户]
#18楼
回复
引用
查看
@装配脑袋
是我想当然了,考虑到Singleton模式的特殊性,这样使用没有错。
@怪怪
对怪怪兄长说声抱歉。
很久没更新了,争取一个月1~2篇吧,:-)
2008-05-10 21:20 |
Angel Lucifer
#19楼
回复
引用
查看
兽交了,LZ神人啊
晕,怎么我输入法第一个是兽交啊,应该是受教!
2008-05-10 21:27 |
lovecherry
#20楼
[
楼主
]
回复
引用
查看
@簡簡單單..
我倾向于脑袋的选择, 第三种。
@sitwo
嗯我记着这事, 不过我翻译的句子比较成问题, 尤其是我和他比, 缺的全是密密麻麻的细节和分析...
@lovecherry
链接里那个才是神人呢...
P.S. 我的是第四个, 这个充分说明了一个人的纯洁性 :P
@装配脑袋
呃, 忘了说了。 是这样, 第一种和第四种方法的效率问题的解决, 可以归结为第一次进入一个过程(这个例子里是初始化), 第二次进入另一个过程(直接返回)的解决。 对这种问题, 我有时把方法换为一个委托属性。
比如
Func<Singleton> CreateInstance {
get { return _CreateInstance;}
}
_CreateInstance可能初始值如下:
delegate() {
Interlocked.CompareExchange<Singleton>(ref _instance, new Singleton(), null);
if(_instance != null) _CreateInstance = delegate() { return _instance; };
return _instance;
}
这样, 后面的调用就全都是直接返回了... 如果你觉得这种方法不会产生新的问题, 我就给更新上去...
@Angel Lucifer
嘿嘿, 那我就搬板凳去了~
2008-05-10 21:54 |
怪怪
#21楼
回复
引用
查看
首先,new Singleton()还是有可能多次执行,当然只有在一开始就有多个线程激烈竞争的情况下才有可能。其次一开始如果有多个线程进入CreateDelegate,他们都会执行到_CreateInstance = delegate() { return _instance; };虽然看上去没有什么问题,但与其费这么大劲,还不如用方法3……
2008-05-10 22:15 |
装配脑袋
#22楼
[
楼主
]
回复
引用
查看
@装配脑袋
对, 我也琢磨着可能会出现你说的情况, 但是考虑它只在最初热闹一会儿, 就懒得再弄得更复杂了, 就像你说的, 已经费了这么大劲了...
其实这种方式还是用在其它地方更合适...
2008-05-10 22:31 |
怪怪
#23楼
回复
引用
查看
无参数的话,俺倾向于第二种。:-)
2008-05-10 22:58 |
Angel Lucifer
#24楼
回复
引用
查看
盹了,坚持看完
2008-05-12 01:41 |
镜涛
#25楼
回复
引用
查看
@怪怪
在第二种写法里,这句:
private static readonly Singleton _instance = new Singleton();
这种内联初始化就是使用静态的构造器(static Singleton() { })了吧。
2008-05-13 13:35 |
生鱼片
#26楼
[
楼主
]
回复
引用
查看
@生鱼片
对, 但是是不是明确的手工写上静态构造器, 会造成我上面说的区别。
2008-05-13 17:49 |
怪怪
#27楼
回复
引用
查看
@怪怪
o,ths
2008-05-13 18:11 |
生鱼片
新用户注册
刷新评论列表
标题
姓名
主页
Email
(只有博主才能看到)
验证码
*
看不清,换一张
内容(请不要发表任何与政治相关的内容)
Remember Me?
登录
使用高级评论
新用户注册
返回页首
恢复上次提交
[使用Ctrl+Enter键可以直接提交]
该文被作者在 2008-05-11 00:37 编辑过
向地震灾区捐赠爱心
博客园首页
社区
Powered by:
博客园
Copyright © 怪怪
导航
博客园
首页
新随笔
联系
订阅
管理
<
2008年5月
>
日
一
二
三
四
五
六
27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
7
公告
微软认为:用远见打赌是公司存在的全部。虽然许多远见最终都会以失败告终,但这并不重要,重要的是他们曾尝试过。
与我互动
给我发短消息
常用链接
我的随笔
我的空间
我的短信
我的评论
更多链接
我的参与
我的新闻
最新评论
我的标签
留言簿
给我留言
查看留言
我参加的小组
生活杂谈
博客园精华集出版小组
我参与的团队
北京.NET俱乐部(2/1110)
我的标签
敏捷(1)
极限编程(1)
XP(1)
单元测试(1)
重用(1)
随笔档案
(68)
2008年5月 (6)
2008年4月 (8)
2008年3月 (12)
2008年2月 (8)
2008年1月 (5)
2007年12月 (8)
2007年11月 (8)
2007年10月 (2)
2007年9月 (11)
搜索
最新评论
1. re: 为什么OO是有本质缺陷的?
收藏,过几年再看。
--金色海洋(jyk)
2. re: 为什么OO是有本质缺陷的?
这样的文章才应该放在首页@_@
--信110
3. re: 为什么OO有本质缺陷?
这文章就不发首页了.., 年纪大了, 胆小了...
--怪怪
4. re: 漫谈数据存取与对象设计
@songcan如果想听真话, 我说一句“OO对我而言有一种莫名的吸引力”, 就已经是“为了OO而学习OO”了。为什么这么说呢, 因为我非常熟悉这种感觉... T _ T几点:1. 不要相信工具, 充...
--怪怪
5. re: 漫谈数据存取与对象设计
@songcan 为什么不能一点一点地应用OO呢? 要么全OO,要么全都不OO吗?不会吧。 我就是一点一点改的,如果感觉OO的某一块对我有利,我就会应用进来,而其他的部分还是过程式的...
--金色海洋(jyk)
阅读排行榜
1. 如何成为一个现代程序员: 使用你的CPU, 而不是内存(8678)
2. 业余程序员宣言: 我们就TM不专业了, 怎么了?(7438)
3. 什么是专业? 谁更专业?(7053)
4. 三问TDD: 单元测试总是好的吗?(5948)
5. 技术算几斤几两又值几个钱?(5693)
评论排行榜
1. 又论社区风气, 与程序员是干嘛地的.(120)
2. 业余程序员宣言: 我们就TM不专业了, 怎么了?(110)
3. 技术算几斤几两又值几个钱?(75)
4. 关于互联网的一些看法(69)
5. 如何成为一个现代程序员: 使用你的CPU, 而不是内存(57)
60天内阅读排行
1. Knuth访谈: 老将出马, 一个顶俩(4067)
2. 随便说两句: 表设计兼一些设计分析的讨论(3822)
3. 也论标准: 统一是啥好事情?(3235)
4. 对Singleton的实现方法做一个总结(2189)
5. 我们应该讨论什么? 就面向对象的讨论所引发的一些思考(2184)