`
gong1208
  • 浏览: 557139 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

一次代码review引发的关于单例模式的思考

阅读更多

              一次代码review引发的关于单例模式的思

一次代码调优中发现一个情况,即我在查看memcached的connection时,发现总是维持在100来个左右,当然这看似没什么问题,因为memcached默认connection有1024个。但是我想的是为什么会有100来个,因为我的memcachedclient的产生采用的是单例模式,so let’s get into the code:

我定义了一个memcachedClientFactory类,主要代码如下:

MemcachedClientFactory{
private MemcachedConnectionBuilder memcachedConnectionBuilder;
private String servers;
private static MemcachedClient memcachedClient;

private MemcachedClientFactory(){
}

private MemcachedClientFactory(MemcachedConnectionBuilder memcachedConnectionBuilder, String servers){
	this. memcachedConnectionBuilder= memcachedConnectionBuilder;
	this.servers=servers;
	}

public static MemcachedClient createClient(){
if(memcachedClient==null){
this.memcahcedClien= new MemcachedClient(memcachedConnectionBuilder.build(),AddrUtil.get(servers));
}
	return this.memcahcedClient;
}
}
}

 那回到最初的问题,为什么会有100多个连接?上面这个写法真的能保证只产生一个连接?很显然是不能,为什么?多线程并发!问题就出在这里,当有多个线程同时进入createClient()方法时,而且刚好都判断为memcachedClient为null,这时候就产生了多个连接。哈,问题找到了。

改进:

public static synchronizd MemcachedClient createClient(){
	if(memcachedClient==null){
this.memcahcedClien=  new
MemcachedClient(memcachedConnectionBuilder.build(),AddrUtil.get(servers));
}
	return this.memcahcedClient;
}

 这样就ok了,改动很简单。程序是没有问题了,而且也能保证只有一个连接。

不过抛开这个问题,我们可以继续就如何解决单例模式下的并发问题深入思考一下。

我总结一下,要解决单例模式在并发下的问题,大概有三种方式:

 

 

1. 不使用延迟实例化,而是用提前实例化。

即程序改写为:

 

Public Class Singleton{
private static Singleton instance=new Singleton();
private Singleton(){};

public static Singleton getInstance(){
   return instance;
}
}

 这样做时,jvm在加载类时就立马创建了该实例,所以这样做的前提是,创建该实例的负担不大,我不比过多的考虑性能,并且我们确认该实例是一定会用到的。其实我前面的代码也完全可以使用这个方式:

MemcachedClientFactory{
private MemcachedConnectionBuilder memcachedConnectionBuilder;
private String servers;
private static MemcachedClient memcachedClien= new
MemcachedClient(memcachedConnectionBuilder.build(),AddrUtil.get(servers));

private MemcachedClientFactory(){
}

private MemcachedClientFactory(MemcachedConnectionBuilder memcachedConnectionBuilder, String servers){
	this. memcachedConnectionBuilder= memcachedConnectionBuilder;
	this.servers=servers;
	}

public static MemcachedClient createClient(){
	return this.memcahcedClient;
}
}
}

 不过,看上去似乎没有问题,但是有隐患,即一旦有人不小心调用了memcachedClient.shutdown()方法,那整个程序就无法再生出新的memcachedClient了。当然这是极端情况了,但是为了代码的健壮,可以再改为:

public static MemcachedClient createClient(){
if(memcachedClient==null){
this.memcahcedClien= new MemcachedClient(memcachedConnectionBuilder.build(),AddrUtil.get(servers));
}
return this.memcahcedClient;
}

  2.  就是使用synchronized关键字。

这么做可以保证同步问题,但是我们知道使用synchronized的开销是很大的,会严重影响性能,所以用这个的前提是,你确认不会经常调用这个方法,或者你创建这个instance的开销不会特别大。是否还可以改进,看 下面。

3. 使用“双重检查加锁“,在getInstance中见识使用同步。写法如下

public Class Singleton{
private volatile static Singleton instance;

private Singleton(){};
public static Singleton getInstance(){
 if(instance==null){
		synchronized (Singleton.class){
	if(instance==null){
	instance=new Singleton();
}
}

}
return instance;
}
}

 这样减少了synchronized出现的次数。当然使用volatile关键字有局限性,就是你的jdk必须是1.5以后的版本。

6
4
分享到:
评论
16 楼 gong1208 2013-04-02  
我只是简单写了下,没想到大家这么热心,给了这么多建议,而且很多建议很好,想的比我深入,十分感谢啊
15 楼 JasonWo 2013-04-02  
获取实例判断使用同步双重锁可将漏洞降到最低,但比较耗资源.
14 楼 jiakechong 2013-04-02  
風一樣的男子 写道
我习惯用静态内部类
public class Singleton{  
      private static class Holder {
           private static final Singleton INSTANCE = new Singleton();
      }
      private Singleton(){};  
  
      public static Singleton getInstance(){  
          return Holder.INSTANCE ;  
      }  
} 


+1
13 楼 greemranqq 2013-04-02  
这样不行,如果两个线程 同时执行了 createClient方法,如果memcachedClient 都是null,那么 会拿到两个 对象,就失去效果了。

还得对 直接调 获得对象的枷加锁方法
12 楼 xiaoyu1985ban 2013-04-02  
建议LZ考虑使用枚举来实现单例模式,具体可以参考《Effective Java》。
11 楼 gnomewarlock 2013-04-02  
3楼的才是正解,review代码必须从根上解决问题。
只有静态内部类才能从ClassLoader机制上避免多次实例化,当然前提是ClassLoader必须是单一的
10 楼 lzxz1234 2013-04-02  
内部类加持基本算是最好的,双重检查应该放弃
9 楼 LewisHe 2013-04-01  
don't act smart. Be simple.

The simple approach you mention will work in most case.
Synchronization never really necessary.
8 楼 learnworld 2013-04-01  
双重检查可能会失效,http://www.ibm.com/developerworks/cn/java/j-dcl.html
7 楼 ricoyu 2013-04-01  
经典的单列模式的三种实现方式,书上都有
6 楼 shadowlin 2013-04-01  
之前看过一篇文章。不过表老了,说双重锁机制(DCL)也不保险的
5 楼 fighting_2013 2013-04-01  
楼主说的其实就是单例模式的双重检查锁定吧
4 楼 jiushiyouke 2013-04-01  
semmy 写道
应该锁变量,也可以提高性能,静态方法锁,是锁住整个类级别的
public static MemcachedClient createClient(){    
    if(memcachedClient==null){    
        syncInit();  
    }    
    return this.memcahcedClient;    
}   
  
public static void syncInit(){
    synchronizd(memcachedClient){
    if(memcachedClient==null){    
        this.memcahcedClien=  new    
MemcachedClient(memcachedConnectionBuilder.build(),AddrUtil.get(servers));  
    }  
    }
}  


你确定你这样写可以运行? 你能对null加锁么?
3 楼 風一樣的男子 2013-04-01  
我习惯用静态内部类
public class Singleton{  
      private static class Holder {
           private static final Singleton INSTANCE = new Singleton();
      }
      private Singleton(){};  
  
      public static Singleton getInstance(){  
          return Holder.INSTANCE ;  
      }  
} 
2 楼 semmy 2013-04-01  
应该锁变量,也可以提高性能,静态方法锁,是锁住整个类级别的
public static MemcachedClient createClient(){    
    if(memcachedClient==null){    
        syncInit();  
    }    
    return this.memcahcedClient;    
}   
  
public static void syncInit(){
    synchronizd(memcachedClient){
    if(memcachedClient==null){    
        this.memcahcedClien=  new    
MemcachedClient(memcachedConnectionBuilder.build(),AddrUtil.get(servers));  
    }  
    }
}  
1 楼 wwwcomy 2013-04-01  
public static synchronizd MemcachedClient createClient(){  
    if(memcachedClient==null){  
this.memcahcedClien=  new  
MemcachedClient(memcachedConnectionBuilder.build(),AddrUtil.get(servers));  
}  
    return this.memcahcedClient;  
} 


这样直接加锁不太好吧  应该判断为空的时候 调用里面的加锁方法:

public static MemcachedClient createClient(){  
    if(memcachedClient==null){  
        syncInit();
    }  
    return this.memcahcedClient;  
} 

public static synchronizd void syncInit(){ 
    if(memcachedClient==null){  
        this.memcahcedClien=  new  
MemcachedClient(memcachedConnectionBuilder.build(),AddrUtil.get(servers));
    }
}


这样不用每次拿实例都经过锁了 只是第一次的时候要经过锁。

相关推荐

Global site tag (gtag.js) - Google Analytics