线程安全和性能之间的博弈问题:

来源:互联网 发布:网络上1米是多少钱 编辑:程序博客网 时间:2024/06/05 07:27

多线程环境下,要保证资源数据安全性,同步锁的使用会带来性能问题。突出在singleton的使用上,lazy initalization不使用,就不会有这个问题,其实首先应考虑的是业务场景里该性能问题是不是一个性能需求

banq: static使用需要和多线程概念联系起来,过分使用Static会导致系统单线程运行,整个系统性能大幅度降低,使得你都无法定位性能问题。
我曾经碰到过一个游戏系统,由于大量使用static 和singleton,导致单线程,速度太慢了。注意:对于一些初学者,单线程开发是最方便的,这也是没有架构设计原因导致的,这样的项目必然失败。

robbin: static方法本身不会影响性能,笼统的来说,由于不需要new对象,也不需要隐式传递对象引用,因此内存消耗少,执行速度快。
但是由于static方法调用本身是不访问对象的,因此方法内部不能够使用非静态的属性,如果你不管三七二十一乱用static方法,必然会造成定义大量的静态属性,这样反而会造成内存消耗大,对象之间偶合性非常高的问题。

因此是否采用static方法不取决于内存消耗和执行效率,而是取决于你的业务逻辑。笼统的来说,如果对象是有状态的,那么需要new一个对象,方法不是静态的,如果对象是无状态的,那么使用静态方法,把类的静态方法当做函数调用来使用。


因为static方法内部不能够使用非静态的属性,所以用singleton替换

[补课]Singleton的性能问题
gigix: 最近我听到了这样一种说法:Singleton模式在多线程环境下存在性能问题。并且,这就成了Singleton模式的一个罪状:因为Singleton在多线程底下有性能问题,因为J2EE是多线程的,所以J2EE底下不应该用Singleton模式。现在我来帮这些擅长过度省略的高手们补补课:Singleton模式,究竟在怎样的情况下才有性能陷阱?

第一个问题,Singleton模式在多线程环境为何遭遇困境?

答案是,采用lazy initialization策略时,如果没有合理的同步(synchronize),各个线程得到的实例可能不是同一个。详情可以参考JavaWorld 2001年的文章:When Is A Singleton Not A Singleton。(某些同志连这篇文章都没听说过,居然也可以抱怨说“找不到这方面的英文资料”,恩,我们说话恐怕还是谦虚谨慎点好。)

举例:
public class MyClass { private static MyClass _instance; public static MyClass getInstance() { if(_instance != null) {_instance = new MyClass();} return _instance;}

如果有两个线程同时调用MyClass.getInstance()方法,就有可能造成MyClass的构造子被调用两次。所以我们需要同步――准确说,恰当的同步。

在C++里面有一种常见的Singleton实现策略叫Double Checked Locking idiom(http://www.javaworld.com/javaworld/jw-01-2001/jw-0112-singleton_p.html,listing 6),但这种实现策略在Java中不生效(这是由于JVM的本性造成的,详情请看这篇文章:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html ),因此如果要在Java中实现lazy initialization策略的Singleton,你就必须采取保守的同步策略,也就是:


public static [b]synchronized[/b] MyClass getInstance() {...}

如果采取保守的同步策略(将整个getInstance()方法同步),多个线程需要获得Singleton实例时就必须在getInstance()方法上排队等待。这就是传说中的“Singleton模式的性能问题”。现在我要提问了:这种性能问题在什么情况下才会出现?

答案就摆在你面前:只有采用lazy initialization策略时,才会存在这样的性能问题。那么如果放弃lazy initialization策略、改用eager initialization策略(即:预先创建好Singleton实例),Singleton模式还会存在这样的性能问题吗?我们把上面的例子改成eager initialization策略看看:

public class MyClass { private static MyClass _instance = [b]new MyClass()[/b]; public static MyClass getInstance() { return _instance;}

我请问,这样的一个Singleton难道还会有什么“性能问题”吗?它付出的代价是更长的初始化时间,获得的收益则是更快并且线程安全的实例获得,而这正是Spring容器对其管理的组件的默认策略。其实这个问题早已有了定论,请看http://www.javaworld.com/javaworld/jw-01-2001/jw-0112-singleton_p.html这篇文章的listing 1和listing 2,Singleton模式的两种正确的实现策略早在2001年就已经讨论清楚了。

作为结论,我提醒某些善于过度简化乃至以讹传讹的高手们:请不要简单地说一句“Singleton模式有性能问题”了事。完整的说法应该是,当采用Lazy Initialization策略时,如果需要经常地获取Singleton实例,则Singleton模式中用于获取实例的方法有可能成为性能瓶颈;如果条件允许采用Eager Initialization策略,则Singleton模式不会带来任何额外的性能开销――如果考虑管理对象池或是新建对象实例的性能开销,Singleton模式能够提升系统的性能。 

banq: 当多线程争用同一个共享资源对象时,才可能出现性能陷井,例如线程互锁。当然,你可以认为这不是Singleton的错,但是在多线程共享一个对象情况下,如果你对Singleton没有陷井认识,而且喜欢简单的话,总是会将这个对象做成Singleton(可以有其他做法),那么问题就来了。

1、Singleton 并不真正产生性能问题,只是需要注意使用Singleton 的时机,不要滥用。

2、只有当多线程争用同一个共享资源对象时,若要解决同步问题,才会产生性能问题,但这不是Singleton 对象的问题,任意对象都可能碰到。


banq: lazy initalization在实际中可以不用,因此回避这个问题,主要带来的不只是一点性能问题,而是可能变成非单态。

“Singleton 并不真正产生性能问题,只是需要注意使用Singleton 的时机,不要滥用。”

是这样,这个问题就象以前ClassLoader问题一样,ClassLoader本身不复杂,甚至程序员名人都对其字节码分析过(个人觉得这是一种典型的向下思维,我推荐搞应用的要有向上思维,注重技术的应用场景,这也是设计模式的要点),其实ClassLoader复杂问题是在其应用场景,如Weblogic/Jboss的EJB、WEB和Ear打包处理中。

所以我说Singletong应用有性能陷井,也是指其应用场景,用的时候要小心,不是一点陷井没有,是有的。

其实,想起一个技术,就要立即想起它的应用场景,这才表面你已经有经验灵活运用它,(不同意:今后请不要一提Singleton就联想到性能有问题。它两之间没有必然联系。)因为任何技术都是有应用前提的。


Doug Lea:
Some one write the following program, he use HashMap to cache some values,You are correct that this is not thread-safe. If this cache canbe accrssed by mulitple threads, you should use a thread-safeMap such as the new java.util.concurrent.ConcurrentHashMap (or,just java.util.Hashtable)

现在有这样一个问题:有这么一个客户端,用户可以定阅世界上所有城市的温度信息,每5分钟客户端刷新一次,那么服务器端肯定要在内存中做个cache,我的想法是做个hashmap来存储,然后这个hashmap位于一个单例中,因为城市太多,而且有的城市定制的人很少,所以采用lazy initialization的方式,只有当第一次被访问的时候,才缓冲该城市信息,并且5分钟后该信息自动失效,看了gxixi的文章,这里只能用同步的方式,这样会显著影响效率,那么究竟应该怎么做那?

如果说几千个数据还可以一次全部初始化,那么如果数据多的多该真么办那? 

不需要同步,又哪来的什么“性能问题”呢

private Map _tempCache;  //HashMap


public Long getTemperature(String city) {
Long result = (Long) _tempCache.get(city);
if(result == null){
result = calcTemperature(city);
_tempCache.put(city, result);
}
return result;
}


虽然HashMap not thread safety
有并发又怎么样呢?并发冲突了又怎么样呢?有什么严重的后果?不妨看看代码:

if(!_cache.contains(city)) { // 现在还没这个城市的温度 // 突然这时候另一个线程访问了,cache里有这个城市的温度了_cache.put(calcTemperature(city)); // 于是,cache里刚放进去的数据被冲掉}

那又怎么样呢?无非是用下一秒钟的温度冲掉了上一秒的而已。除非真是《后天》的那种情况,否则我可以很放心地说,这两个温度值就是一样的――如果它们不一样,那也是温度传感器的问题。你根本不需要一秒钟以内的准确性,那你为什么要去保证写入的互斥? 


fengyun:

我也从来没有听说过Singleton的性能问题。
我们是用Java做邮件系统的,MTA都是用Java实现的,涉及到众多
多线程的程序,性能方面考虑的也不少。没有觉得Singleton会带来什么性能问题,如果说性能问题也只是说因为不合理的程序而导致的。
与Singleton没有关系。
一般我们性能优化遵循一些基本的准则:
1. 减少内存的开辟,复用提高效率。
2. 减少对象的构造,复用已经创建的对象。
3. 减少消耗时间的操作频率(采用cache等)
4. 用基于byte[]的操作来代替字符串的操作
....

根据上面的 2 3 原则来分析下面的程序:

这样的程序,如果大部分时间耗费在calcTemperature上,那么这个程序应该用下面的程序来代替:

private Map _tempCache;

public Long getTemperature(String city) { 

if(!_tempCache.contains(city)) {
Long result = calcTemperature(city);
_tempCache.put(city, result); 
}
return (Long) _tempCache.get(city);
}


//替换程序
private Map _tempCache;
public Long getTemperature(String city)

//直接获取
Long result = _tempCache.get(city);
if (result == null) {
//如果不存在 Double Checking
synchronized(this) {
//因为内存操作比起计算或者从数据库获取快很多
result = _tempCache.get(city);
if (result == null) {
result = calcTemperature(city);
_tempCache.put(city, result); 
}
}
}
return result;


原创粉丝点击