[补课]Singleton的性能问题

04-10-19 anonymous
         

最近我听到了这样一种说法: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;
  }
<p>

如果有两个线程同时调用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() {
  ...
}
<p>

如果采取保守的同步策略(将整个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;
  }
<p>

我请问,这样的一个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模式能够提升系统的性能。

         

5
robertfang
2004-10-25 22:23

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

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

anonymous
2004-10-25 23:58

>

>

> 现在有这样一个问题:有这么一个客户端,用户可以定阅世界

> 纤谐鞘械奈露刃畔?,每5分钟客户端刷新一次,那么服务器端

> 隙ㄒ谀诖嬷凶龈cache,我的想法是做个hashmap来存储,然?

> 这个hashmap位于一个单例中,因为城市太多,而且有的城市定?

> 的人很少,所以采用lazy

> initialization的方式,只有当第一次被访问的时候,才缓冲该

> 鞘行畔?,并且5分钟后该信息自动失效,看了gxixi的文章,这里

> 荒苡猛降姆绞?,这样会显著影响效率,那么究竟应该怎么做?

> ?

>

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

> 喔谜婷窗炷??

你想清楚自己究竟要做什么了吗?

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);
}
<p>

请问你,这里有任何一个地方需要同步吗?如果压根不需要同步,又哪来的什么“性能问题”呢?

robertfang
2004-10-26 09:41

你把这个问题避开了,你的MAP是什么?hashtable本身就是同步的,用在这里本身就是同步性能陷阱。

用hashmap,完全没有同步问题,可是无法保证1读写同一个键的互斥性,2会出现两个线程并发给一个key赋值得现象,如果值不是简单的long而是复杂的数据库查询结果的话,还是有很大隐患的。

我觉得解决方案还在这篇文章上,

http://www-900.ibm.com/developerWorks/cn/java/j-jtp07233/

或许你有什么更好的方法?

于诸君共勉:)

anonymous
2004-10-26 10:29

>>用hashmap,完全没有同步问题,可是无法保证1读写同一个键的互斥性,2会出现两个线程并发给一个key赋值得现象,如果值不是简单的long而是复杂的数据库查询结果的话,还是有很大隐患的。

照这个需求,“温度信息”是每过5分钟才更新一次的,我为什么要去保证它一秒钟内两次写操作互斥?如果说同一瞬间有两个请求过来了,很好,由它们去吧,那又怎么样呢?这个需求根本就用不着保证什么同步性互斥性,为什么要强加给它呢?

13Go 1 2 3 4 ... 13 下一页