偶是一个热爱学习的好小孩, 顺手去google一把, keyword是:
java "破坏结构"
很不幸, google告诉偶结果只有2页, 而且结果是风马牛不相及......
那么, "破坏结构"是你们自己翻译的吧? 不知道可否教育偶一下对应的英文术语是什么, 偶用英文google看看, 说不定能找到相关的资料来好好学习, 天天向上......
偶是一个热爱学习的好小孩, 顺手去google一把, keyword是:
java "破坏结构"
很不幸, google告诉偶结果只有2页, 而且结果是风马牛不相及......
那么, "破坏结构"是你们自己翻译的吧? 不知道可否教育偶一下对应的英文术语是什么, 偶用英文google看看, 说不定能找到相关的资料来好好学习, 天天向上......
写了一段程序,大概意思就是
主线程创建THREAD_COUNT个线程,这些线程等待主线程一声令下,
并发地往一个非同步的HashMap中put一对相同的键值对,主线程
等待这些线程都完成操作后,检查这个HashMap的size,如果size>1
那说明这个HashMap有问题了,否则重复进行以上过程。
我的P4 2.8 700M内存的机器过了大概不到10秒钟出现size=2
当然出现两个相同的键也许算不了什么,但你怎么保证不会出现更
糟糕的情况呢?
你不要说现实中不可能出现这么高的并发,那就没意思了。
|
哇, 好cool的代码呀, 偶刚刚读大一, 是Java的初学者, 原来在Java里面测试多线程并发要写得这样复杂呀.....
闲话少说, 偶尝试着在你的代码
System.out.println("\nsize: " + map.size());
后面小心地加上一句
System.out.println(map.get("0"));
(嗯, 应该没有写错吧??)
就是想看看结果是什么? 难道不是1? 难道会出来一个异型?
偶的机器是amd2500 1G memory, 偶跑了很多次, 结果都是1......
偶上次对于你们所说的"破坏结构"所做的猜想后果一条也没有发生:
------
偶下次用map.get(key)获得的对象是null?
或者获得的对象是一个异型?
还是程序会抛出runtime exception?
甚至导致JVM crash?
------
cache照样work, 地球照样在转, size() changed? hey, who cares?
你们在第一页里面提到闻所未闻的cached object可能被"破坏结构"到底怎么才会发生呢??
上面的程序我每次都可以成功使size>1
ok, another program
THREAD_COUNT个线程并发往一个非同步HashMap中分别写
Integer 0...THREAD_COUNT-1
写完后主线程检查是否Integer 0...THREAD_COUNT-1 都存在于这个HashMap中,并打印它的size()及toString()
这个应该很快出现,而且那时你可以看到size虽然==THREAD_COUNT,
但toString中却没有这么多值。
|
嘿, 偶没有说清楚, 还是你没有看清楚?
map.size() 变成了2, 是没错, but, who cares?
偶是建议你在后面加上一句
System.out.println(map.get("0"));
看看你们所说被破坏了结构的cached object是什么?
扔出一堆代码, 还不是证明了gigix最初的观点:
这种cache更本不用同步, 哪怕有并发冲突发生, 也不会对你的cache功能有任何影响.
ok,那这一段程序呢?
有些数据没有被put到Map中算不算呢?
难道说:没有就没有嘛,再put一次不就好了!
不过我看其实也无所谓啦,不就是个cache嘛,大不了再到后台取一次得了,反正咱们的应用也不care,用户也看不到啦。
不争了,ok?
不争了? 累了?
证明了2点,
1. 破坏结构纯属虚构
2. 并发会影响hashmap cache的功能也纯属虚构
嗯, 这个好像gigix在第一页里面就讲过了......
OK, gigix唯一错误就是, cache应该是这样写的, 才是unbreakable:
private Map _tempCache;
public Long getTemperature(String city) {
Long result = (Long) _tempCache.get(city);
if(result == null){
result = calcTemperature(city);
_tempCache.put(city, result);
}
return result;
}
那我就再争一争吧,我也是够无聊的:)
我们这些小人物也争不出啥结果来,还是请个权威来说两句吧。
昨天写了封email给Doug Lea,Doug Lea何许人也?不知道的就google一下吧。
其实这个问题本不该麻烦人家的。
如果你还是不信,那你就直接去请教java collection framework的作者
Joshua Bloch吧。
到此为止了!
|
gigix,看了你的主贴,你提到如果采用:
|
当lazy initialization结束后,getInstance() 所要做的唯一的事只是返回一个对象的引用,这种性能问题在真实应用中
是否可以忽略不计?若产生性能问题,究竟到底在什么情况下才会发生。
大家都挺认真的,gigix的主贴我还是看得不够明白,可能我这人喜欢看平静理性的陈述,当然,有时我的言论让别人无法平静理性,这是我应该纠正的问题。
这件事情都震动了Dou Lee,真是不好意思,这个现象让更明白,有些事情好像确实争论不出结果来的。cafebabe 真是辛苦了。其实,HashMap不是线程安全是常识。
从newjoy 看出,好像说Singleton创建时有些性能误用区,这个只是小小的陷井,其实我觉得讨论方向有些问题。
那么,Singleton模式,究竟在怎样的情况下才有性能陷阱?
这个帖子说的案例才是有陷井的可能性:
http://www.jdon.com/jive/thread.jsp?forum=121&thread=16742
当多线程争用同一个共享资源对象时,才可能出现性能陷井,例如线程互锁。当然,你可以认为这不是Singleton的错,但是在多线程共享一个对象情况下,如果你对Singleton没有陷井认识,而且喜欢简单的话,总是会将这个对象做成Singleton(可以有其他做法),那么问题就来了。
具体案例代码我以后再碰到,会贴在这里。
Jdon讨论对事不对人,倡导理性讨论。
那现在应该基本观念达成一致了。我不知道我的理解是否正确:
1、Singleton 并不真正产生性能问题,只是需要注意使用Singleton 的时机,不要滥用。
2、只有当多线程争用同一个共享资源对象时,若要解决同步问题,才会产生性能问题,但这不是Singleton 对象的问题,任意对象都可能碰到。
BTW:
个人认为 Doug Lea 的邮件并没有帮助cafebabe 证明什么。他只是告诉你HashMap不是线程安全的。
同意
但是我的问题好像还是没有解决,
因为我并不认为当lazy initialization结束后,
getInstance() 由于使用了“synchronized”就影响了整体的性能,这在真实应用中是否可以忽略不计?
gigix指出的“若需要经常地获取Singleton实例,则Singleton模式中用于获取实例的方法有可能成为性能瓶颈;”,我觉得应该更夸张描述为“需要高频度并发的获取Singleton实例,则Singleton模式中用于获取实例的方法有可能成为性能瓶颈”,但这种情况在我们的实际应用中真的存在么?若真的存在,我们是否有别的办法可以解决?
具体何时该用Eager Initialization,何时该用lazy initialization应该看实际的应用需求进行评估。不存在好与不好之分,也没有统一的标准。
所以我同意gigix的观点,今后请不要一提Singleton就联想到性能有问题。它两之间没有必然联系。
synchronized产生的性能问题,是每一个涉及多线程操作的所有对象同样要面对的问题,而非Singleton一个。
看看 Hashtable 不也使用了大量的synchronized:
|
to newjoy, lazy initalization在实际中可以不用,因此回避这个问题,主要带来的不只是一点性能问题,而是可能变成非单态。
“Singleton 并不真正产生性能问题,只是需要注意使用Singleton 的时机,不要滥用。”
是这样,这个问题就象以前ClassLoader问题一样,ClassLoader本身不复杂,甚至程序员名人都对其字节码分析过(个人觉得这是一种典型的向下思维,我推荐搞应用的要有向上思维,注重技术的应用场景,这也是设计模式的要点),其实ClassLoader复杂问题是在其应用场景,如Weblogic/Jboss的EJB、WEB和Ear打包处理中。
所以我说Singletong应用有性能陷井,也是指其应用场景,用的时候要小心,不是一点陷井没有,是有的。
其实,想起一个技术,就要立即想起它的应用场景,这才表面你已经有经验灵活运用它,(不同意:今后请不要一提Singleton就联想到性能有问题。它两之间没有必然联系。)因为任何技术都是有应用前提的。
“synchronized产生的性能问题,是每一个涉及多线程操作的所有对象同样要面对的问题,而非Singleton一个”
这个问题如果有我前面向上思维,那么就很容易注意到。并只有多线程操作同一个共享对象(读取没有问题,是写修改的情况)就会发生,一般如何做一个共享对象:无外乎将那个共享对象作为参数传进来,这是Jive源码中ForumFactory作为参数传来传去的原因;一般这种做法很麻烦,谁喜欢一个东西一直在所有方法参数中出现呢?那么简捷的方式就是使用Singleton。
最后,还是用外国人的说话有说服力,picocontainer的一篇文章:
http://www.picocontainer.org/Singleton+antipattern
picocontainer作者宣称Singleton其实是反模式,不能算模式!
那我想原先gigix提的:
“请不要简单地说一句“Singleton模式有性能问题”了事。”的观点咱们应该同意吧?
随口就说出一句“Singleton模式在多线程环境下存在性能问题。”咱们应该反对吧?
BANQ也指出“想起一个技术,就要立即想起它的应用场景”。那我们也应该针对具体的应用场景去评价使用一个技术的优劣,而不是在任意情况下简单总结一句话。
“所以我说Singletong应用有性能陷井,也是指其应用场景,用的时候要小心,不是一点陷井没有,是有的。”
这句话一点没错,但我一样也可以说:
“所以我也说 CopyOnWriteArrayList 应用有性能陷井,也是指其应用场景,用的时候要小心,不是一点陷井没有,是有的。”
“所以我也说 Hashtable 应用有性能陷井,也是指其应用场景,用的时候要小心,不是一点陷井没有,是有的。”
我只是一直想不通为啥会有人对Singleton有歧视,偏偏要针对Singleton?
说老实话,自从出现了Spring,我们已经很少自己去实现Singleton了。
您说的“谁喜欢一个东西一直在所有方法参数中出现呢”,Spring 倒是一个很好的解决方式对不?当时是注入方式,而非频繁的getBean("xxx")