设计模式之单例模式

         工作了两年,项目中也用到了不少设计模式,但是一直没有总结,这次总结一下。

         单例模式,顾名思义,class仅仅被实例化一次,英文为“singleton”,在之前学习php的时候,单例异常简单,就是最野蛮的办法:

          

<?php
$instance = null;

function getInstance() {
    if(!$instance) {
        $instance = new A();
    }
    return $instance;
}

 

       当我最开始学习Java的时候,我以为Java的单例也是这么回事:


public A getInstance() {
    if(null == $instance) {
          $instance = new A();
    }
    return $instance;
}



       但完全不是那么回事儿啊,php是一个脚本语言,整个代码的生命周期可以认为是request级别的,而java不一样,在web 容器 destory之前,一直有效,而这个过程中会有N个线程在跑,我们知道,线程之间数据共享,所以就悲剧了。


        当然,我们可以将其改进一下,弄成static,我们知道,static的属性可以保证单例,如:


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

    private Test() {}
    public static void main(String[] args) {
        Test test = Test.getInstance();
    }
    public static Test getInstance() {
        return instance;
    }

}



     为什么static可以保证单例呢,我们使用javap -verbose Test.java可以看到:


 

static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: new           #4                  // class Test
         3: dup
         4: invokespecial #5                  // Method "<init>":()V
         7: putstatic     #3                  // Field instance:LTest;
        10: return

    在类被实例化之后,会自动new Test(),并且将Test这个static变量引用到这个heap位置,在真正外部方法调用之前,这个变量就已经有值了,所以可以保证单例,但是这样会有一个问题,不能"lazy load",也就是会浪费内存,在最坏的情况下,Test被实例化之后,getInstance一直没有被调用到,那么heap区域的内存就浪费了。那么我们怎么优化呢,早一点的程序员可能习惯下面的这种写法(double check lock):


private static Test instance;

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



   这种看着很完美的方案其实有几个问题:


   1、代码看着真心蛋疼

   2、并发的时候有问题,你可能会想,都用了synchronized加锁了,还有什么问题?我们知道,在jmm(java memory model)中定义了在多核情况下线程的调度规则,我们的线程变量其实运行在每一个工作线程中(work memory),这个数据是main memory的copy,那么当work memory的instance变量变化之后,这个变化可能不会很及时的同步到main memory,所以导致其他线程读取的值并不是想要的值,更多的信息可以看一下:http://www.iteye.com/topic/260515/,那我们怎么解决这个问题?很简单,给这个变量加上volatile,有关volatile的内容大家可以参照oracle官网jmm的内容。

   后面大家又开发出另外一种单例的写法,比上一种更优雅,利用内部类。

   

public class Test {
    private static class InstanceHolder {
        private static final Test instance = new Test();
    }
    public static Test getInstance() {
        return InstanceHolder.instance;
    }
}




    那么这种方案是否就没有问题了呢,其实答案是No。

   1、当存在多个classloader时,它并不是单例的:我们知道同一个class在不同的classloader其实是不同的;

   2、多jvm时非单例;

   3、在机器间传输时非单例,在反序列化是会创建一个新的实例,如果想避免这种情况,需要实现readResolve方法,如:

      

private Object readResolve() {
    return instance;
}




    4、多机器,当然,如果要解决这个,可能需要借助db等工具了。

   其实上面这几点内容并没有什么卵用,在真正的项目中,耗费一点内存是无关紧要的,机器不值钱,人力成本才是最高的,所以个人感觉还是这种方式更靠谱点:



private static Test instance = new Tetst();

public static Test getInstance() {
     return instance;
}

private Test() {}



     这里直接私有化构造方法,然后强迫外部调用方使用getInstance来获取Test的实例来保证单例。


    这种有办法破坏掉它的单例性吗,其实很简单,使用Reflection api,分分钟破坏掉它,我网上随便搜了一下,就有文章,如下:http://blog.csdn.net/lws332969674/article/details/8125893

    其实还有另外一种方案,就是使用enum来保证单例,这里我就不写了,网上搜了一篇文章,如下:http://segmentfault.com/a/1190000000699591



你可能感兴趣的:(java,单例,设计模式)