工作了两年,项目中也用到了不少设计模式,但是一直没有总结,这次总结一下。
单例模式,顾名思义,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; }
当然,我们可以将其改进一下,弄成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 {}; 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() {}
这种有办法破坏掉它的单例性吗,其实很简单,使用Reflection api,分分钟破坏掉它,我网上随便搜了一下,就有文章,如下:http://blog.csdn.net/lws332969674/article/details/8125893
其实还有另外一种方案,就是使用enum来保证单例,这里我就不写了,网上搜了一篇文章,如下:http://segmentfault.com/a/1190000000699591