单例模式
1.单例模式的设计意图是什么?它要解决一个什么问题?什么时候可以使用它?
2.它是如何解决问题的?
3.结构图是怎样的,有哪些关键代码
4.能够想到这个设计模式的两个应用实例,一个生活中,一个软件中
5.这个模式的优缺点是什么?在使用时要注意什么
单例模式的设计意图
单例模式设计的意图是 为了节约系统资源,确保当前的类在进程的生命周期中只会存在一个实例,也就是说当前类的构造方法在进程的生命周期中只会被调用一次。
如果有些类占用资源太多,或者如果有多个实例会难于管理,比方说线程池,数据库连接池之类的,这些需要使用到单例模式
单例模式是如何解决问题的?
因为在进程中只存在唯一的一个实例,所以在任意时刻通过该类拿到的永远是同一个对象,实现了资源的复用
单例模式的实现与结构图
目前实现单例模式的方式著有要三种 :静态内部类(推荐),枚举(一般),双重检查锁 DCL(一般)
要实现单例要做的几件事
构造器必需私有化,也就是说外部不能调用到自己的构造方法
拥有一个全局的私有静态变量
提供一个访问该私有变量的静态方法
静态内部类的实现方式
静态内部类实现单例的原理是 利用 JVM 的 类加载机制实现的。类加载时会在初始化阶段为所有的静态变量赋值。
其典型的代码如下:1
2
3
4
5
6
7
8
9private static class SingleHodler{
private static final xxx instance = new xxx();
};
private xxx{}
public xxx getInstance(){
return SingleHodler.instance;
}
枚举
枚举没啥好说的,其实现原理与静态内部类一样
但是枚举要注意一个反序列化生成对象的问题,通过序列化可以将一个对象写入到磁盘,然后要使用的时候通过反序列化可以得到一个新的对象,这就有可能使得单例模式失效。
反序列化提供了一个很特别的钩子函数,类中具有一个私有的被实例化的方法 readResolve ,我们可以通过控制该方法控制对象的反序列化,要杜绝反序列化生成新对象可以实现如下方法:1
2
3private Object readResolve(){
return instance;//返回自己创建的单例
}
这个方法是底层通过反射调用的
双重检查锁(double ckeck lock)
双重检查锁的目的是为了解决多线程下单例的问题。其使用的volatile关键字在JDK1.5以后才有,并且volatile的禁止指令重排机制会屏蔽虚拟机的一些优化操作,因此并不是单例的一种完美实现方式。
如果static变量不添加volatile关键字是有可能导致单例失效的,其具体原因如下:
一个对象的创建过程由这么几步组成,1.分配内存 2.初始化 3.将instance对象指向分配的内存空间。那么由于JVM允许在执行时进行指令优化,如果不添加volatile关键字,那么2与3的执行顺序是不确定的,有可能是1-2-3,也有可能是1-3-2.如果是1-3-2,3先执行,2未执行,如果此时切换线程B,此时instance在线程A中已经执行过3了,所以instance不为空,线程B会直接取走instance,在使用的时候就会报错,这就是DCL失效的问题
单例模式的优缺点
优点:
提供了唯一对象的受控访问,可以严格控制用户的访问
由于内存中只存在一个对象,节约了系统资源
缺点:
由于单例模式中没有抽象层,所以扩展会很困难
单例类的职责过重,在一定程度上违反了单一职责原则
在很多面向对象语言中都提供了垃圾自动回收机制,如果实例共享对象长时间没有被引用,会被收集器回收,再次使用时会重新创建对象,此时有可能会导致共享对象的状态丢失
目前推荐使用的是静态内部类与枚举
为了防止反射与反序列化重新创建对象由如下解决方式:如果是反射,可以在构造函数中判断当前实例是否存在,如果已经存在则抛出异常,反序列化前面已经讲过了不再重复。