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

设计模式随笔(二)

阅读更多

单例模式

这个一个看起来最简单,使用起来却最易出错的模式,因为要完完全全理解这个模式,需要对多线程安全、java内存模型有着很深刻的认识才行。

 

最原始的实现方式:

 

public class Singleton{
     
     private final static Singleton instance = new Singleton();

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

 

这是最简单也最容易想到的一种实现方式,而且它也确实能够正确工作。但是,它是在类初始化的时候就构造了这个单例对象,而不是在用户第一次请求这个对象时。这对于追求完美的人来说是不可容忍的。行吧,那就改成“延迟构造”的方式吧:

 

public class Singleton{
     
     private  static Singleton instance = null;

     public static synchornized Singleton  getInstance(){
            if(instance == null)    instance = new Singleton();
            return instance;
     } 
}

 

这样虽然解决了前者“浪费资源”的问题,但却引入了新的问题:getInstance()明明只有在第一次访问的时候才会构造对象,其他任何时候调用都是直接返回结果,那这里干嘛要对整个方法同步?简直就是“过多进行了同步”。so,接着改贝:

 

public class Singleton{
     
     private  static Singleton instance = null;

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

这段代码看似解决了问题,实际上却是不能正常工作的。然而却和上一段代码没有本质的区别。比如线程A先调用getInstance(),此时instance为null,则进入同步块准备构造一个Singleton实例。此时线程B调用getInstance(),此时instance仍然为null,于是进入if语句等待进入同步块。于是,表面上是单例模式,实际上每一个线程都拥有了各自的对象。再接着往下改:

 

public class Singleton{
     
     private  static Singleton instance = null;

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

 

在每次构造之前进行一次判断,这样总该可以了吧。然而这段代码还是不能正常工作的。这其中涉及到了java中著名的DCL失效问题。

 

java中的线程同步保证一个线程B能够看到另一个线程A的处理结果,但是有个前提是A和B必须是在同一个对象上保持同步。

<!----><!----><!----> <!---->

 

同时,java程序的执行并不一定会按照源代码的顺序执行,可能会经过指令重排等优化。java对编译器和虚拟机的要求是“as-if-serial ”,即只要能够达到和严格顺序执行时的效果一样,指令执行的先后顺序可以随意安排。

 

因此,对于上面这段代码,就存在着这么一种可能:线程A调用getInstance(),此时instance为null,则进入同步块构造一个Singleton实例。当它分配了内存给instance,但还没有初始化完成时,线程B调用了getInstance(),此时它判断instance是不为NULL,于是直接返回instance,从而会造成不可预知的结果。

 

那么,到底还有什么更好的方法可以实现单例模式呢?

如果使用的是JAVA5以后版本的话,可以通过将instance变量设置为volatile。java5以后对volatile语义做了更改。java5以前volatile的语义如下所示:

 

while the JMM prevents writes to volatile variables from being reordered with respect to one another and ensures that they are flushed to main memory immediately, it still permits reads and writes of volatile variables to be reordered with respect to nonvolatile reads and writes.

 

从上可知,在java5以前使用volatile的实现方式仍然是可能存在并发错误的。java5以后volatile变量的读写操作与synchoronized 有了相同的涵义,可以保证线程安全。但是这么做的话效率就与synchoronized相差无几了。

 

另一种更好的方法如下:

 

public class Singleton{

    static Class  SingletonHolder {

            static Singleton instance = new Singleton();
    }

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

 

改方法巧妙地利用了”java中内部类在第一次使用他时才装载“这一特性,既保证了效率,又避免了同步开销。

分享到:
评论
3 楼 lobin 2010-02-08  
赞最后一种方法?   那还不如就第一种
2 楼 lionheart 2008-09-11  
竟然没注意到这一点,多谢提醒! 现在已经改过来了。
1 楼 lchlrb 2008-09-10  
  前面的几段代码 声明静态变量时,已经创建new了。。。还需要判断是null吗? 赞最后一种方法。论坛上有过讨论。

相关推荐

Global site tag (gtag.js) - Google Analytics