JAVA与23种设计模式之间的暧昧关系(一)单例模式
什么是单例模式?官方说,就是一个类中只保有他的一个实例,然后将提供给全局使用,简而言之,就是说,我调用的时候,我只需要实例化一次就好了。
那么,为什么要这样做?其实,这样做的好处就是防止类的泛滥实例化。因为我们知道Java是有垃圾自动回收机制,但是,说到自动,带来一个最大的弊端就是你不能把控他,也就是说如果你不能够随自己的心意去回收他,(当然,也是可以通过构造回收类去搞,但是,一般程序都不这样做),那么久而久之,就会照成很多不必要的麻烦,比如内存溢出啊,什么乱七八糟的, 这个时候,如果你用某种技巧性的方式进行勒令他不能实例化或者什么,由你统一生成,这个时候,单例模式的思想就出来了!
我们知道,Java的一个类要使用,只能通过new一个对象的形式去做,(反射的方式除外),那么怎么才让他不能new呢?这个时候,我们可以想到采用java的修饰符,顺道复习下吧,private,私有的,就是说,只有当前类可以使用,protected,保护的,既然是保护,就是当前类和他的子类可以使用。默认修饰符,就是什么都不加,同一个包可以使用,public,都可以访问,还有其它向static什么的,可以去百度下作用。很好,这个时候我们突然发现,原来有个私有的属性,那么我们是不是可以搞点事情出来?
首先,java我们要知道,new完以后会干嘛?当然是调用构造函数,所以,这个时候,我们可以针对构造函数搞事情,嘿嘿,把构造函数私有化呗,这样的话,不就不能new了?也就是说,不能实例化,但是不对啊,既然不能实例化,那么我怎么实例?这个时候就要了解private的属性,它的意思是说只能自己访问是不是,很好,既然只能自己访问,那么我们在当前类里面实例化一个当前类不就得了?说了那么久,我们来看下代码是怎么样的。
public class SingleClass{
private SingleClass(){}
public SingleClass instance = new SingleClass();
}
不对,有些看官肯定觉得不顺眼,怎么可以直接用在这里面实例化?那不也可以实例化很多个吗?好了,别急,待我慢慢说来。首先,这个代码是有问题的,既然刚才说了, 这样并不是单例,它仍然还可以生成很多个实例,既然这样,我们先让的instance私有化一下,因为将这个对象隐藏起来,为了后面做处理,比如说,我想让你什么时候生成就什么时候生成对不对,不对,如果私有化以后,不就不能获取实例了吗?简单。这个时候可以提供一个方法让后面调用嘛,看下代码呗。
public class SingleClass{
private SingleClass(){}
private static SingleClass instance = new SingleClass();
public static SingleClass getInstance(){
return instance;
}
}
好了,简单的单例模式就出来了,为什么加static,是因为static有这样的作用(static变量是指不管类创建了多少对象,系统仅在第一次调用类的时候为static变量分配内存,所有对象共享该类的static变量,因此可以通过类本身或者某个对象来访问类变量。),所以,这种模式叫做饿汉式,因为就像一个饿了的人,急于吃东西,所以,在装载类的时候就开始创建实例了,这样做的好处是用空间换取时间。
除了饿汉式,还有一种叫做懒汉式,也就是说,我不想装载的时候实例化,我要你调了才弄,有点像是你踢了他一下才开始动,不踢我就不挪动一样。看下面的代码:
public class SingleClass{
private SingleClass(){}
private static SingleClass instance ;
public static SingleClass getInstance(){
if(instance==null)instance=new SingleClass();
return instance;
}
}
代码就不解释了,这种方式的好处是在加载类的时候很快,不过如果你要获取实例,就可能会比较慢,毕竟人家是在你获取的时候才开始干活嘛(针对于饿汉式来说的)。当然,其实这两种做法都一般般,毕竟没有考虑到线程同步啊等等问题,而且还会照成一些不好的(自行百度),既然这样,我们是一个有追求的人,那么这个时候是不是应该有一个新的写法?好了,请看代码:
public class OptSingleton {
private volatile static OptSingleton instance=null;
private OptSingleton() {}
public static OptSingleton getInstance(){
//先检查实例是否存在,如果不存在才进行下面的同步代码块
if(instance==null){
//同步代码块,线程安全的创建实例
synchronized (OptSingleton.class) {
//再一次检查是否存在,如果不存在才真正的创建
if(instance==null){
instance = new OptSingleton();
}
}
}
return instance;
}
}
这种实现方式既可以实现线程安全地创建实例,而又不会对性能造成太大的影响。它只是第一次创建实例的时候同步,以后就不需要同步了,从而加快了运行速度。 (由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。)也就是说,虽然可以使用“双重检查加锁”机制来实现线程安全的单例,但并不建议大量采用,可以根据情况来选用。
下面还有另一种方式:
public class OptGoodSingleton {
private OptGoodSingleton(){}
/**
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
* 没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载
*/
private static class SingletonHolder{
/**
* 静态初始化器,用JVM来保证线程安全
*/
private static OptGoodSingleton instance = new OptGoodSingleton();
}
public static OptGoodSingleton getInstance(){
return SingletonHolder.instance;
}
}
当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。 这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。
好了,到此,单例模式就介绍到这里,如果各位看官觉得还有更好的代码实现方式,不妨在后面评论下,嘿,如果觉得好,请关注下我并点赞,后面会有更多设计模式系列哦,嘿嘿。