设计模式之单例模式
1 什么是单例模式
在维基百科中单例模式定义为
单例模式,也叫单子模式,是一种常用的软件设计模式,属于创建型模式的一种。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。
通俗点说单例模式就是该类只有一个实例存在
2 单例模式实现形式
2.1 饿汉式(建议使用)
饿汉式单例模式是指,不管该类是否使用,在类加载阶段就已经实例化,此情况下是线程安全的
实例:
public class HungrySingleton {
private static final HungrySingleton INSTANCE = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return INSTANCE;
}
}
但是如果该类从来未被使用,使用饿汉式单例模式会耗费一定内存
2.2 懒汉式
懒汉式单例模式指,只有在需要对象的时候才创建对象
public class LazySimpleSingleton {
private static LazySimpleSingleton instance = null;
private LazySimpleSingleton(){}
public static LazySimpleSingleton getInstance(){
if(instance == null){
instance = new LazySimpleSingleton();
}
return instance;
}
}
以上在多线程情况下线程不安全,比如两个线程Thread1和Thread2同时执行到if(instance == null),此时Thread1执行new之后,Thread2继续执行new这时创建了两个对象,不符合单例模式,所以考虑加锁
public class LazyLockSingleton {
private static LazyLockSingleton instance = null;
private LazyLockSingleton (){}
public synchronized static LazyLockSingleton getInstance(){
if(instance == null){
instance = new LazyLockSingleton ();
}
return instance;
}
}
以上可以实现线程安全,但是如果在getInstance方法之后立即执行业务逻辑,synchronized锁会锁住不与要加锁的代码片段导致效率低下,下面将锁粒度细化
public class LazyLockSingleton {
private static LazyLockSingleton instance = null;
private LazyLockSingleton (){}
public static LazyLockSingleton getInstance(){
if(instance == null){
synchronized(this){
instance = new LazyLockSingleton ();
}
}
return instance;
}
}
但是这样仍然会出现问题,Thread1和Thread2同时执行到if(instance == null)并判断为true,此时两个线程竞争锁,假如Thread1抢到了锁,Thread2在外面等待,当Thread1执行完毕,Thread2抢到了锁,同样会创建对象,导致线程不安全,所以考虑在锁里面再次加入if(instance == null)进行判断,称之为双端校验锁(DCL),代码如下:
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton instance = null;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
if(instance == null){
synchronized (instance){
if(instance == null){
instance = new LazyDoubleCheckSingleton();
}
}
}
return instance;
}
}
上述通过双端校验实现了一定情况下线程安全,但是CPU执行new LazyDoubleCheckSingleton();语句的指令有如下阶段:
1.在堆中为对象开辟内存空间
2.在空间中创建对象
3.将引用指向该对象
4.开始使用
由于CPU执行指令时会交叉执行,会引起指令重排,假如在1执行完之后执行3会导致对象还未实例化就将该引用指向内存空间,假如Thread1执行到3Thread2执行到if(instance == null)这时候instance不为空,会直接返回一个未初始化的对象,导致线程不安全,所以加volatile关键字防止指令重排序
3 破坏单例模式的方式及解决方案
上述饿汉式和DCL+volatile实现的单例模式是线程安全的,在通常情况下只有一个实例,但是可以通过一些不正常的手段破坏单例模式,比如反射暴力创建对象和序列化
3.1 反射破坏单例
通过反射获取构造方法,然后对构造方法setAccessible(true)就可以创建对象
public class ClassDestroy {
public static void main(String[] args) throws Exception {
HungrySingleton hungrySingleton1 = HungrySingleton.getInstance();
Class<?> clazz = Class.forName("cn.huan.design.singleton.hungry.HungrySingleton");
Constructor<?> constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);
HungrySingleton hungrySingleton2 = (HungrySingleton) constructor.newInstance();
System.out.println(hungrySingleton1 == hungrySingleton2);
}
}
以上破坏单例可以在构造函数中加入判断拦截强制结束程序提示不能该类不能创建多个实例
private HungrySingleton(){
if(INSTANCE != null){
throw new RuntimeException("该类不能创建多个实例");
}
}
3.2 序列化破坏单例
将单例类实现Serializable接口,开启序列化权限,将单例模式生成的对象序列化到文件中,然后再将文件反序列化为对象,发现前后两个对象不相等,破坏了单例模式
public class SerizableDestroy {
public static void main(String[] args) throws Exception {
HungrySingleton instance1 = HungrySingleton.getInstance();
FileOutputStream fos = new FileOutputStream("hungrySingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance1);
oos.close();
fos.close();
FileInputStream fis = new FileInputStream("hungrySingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
HungrySingleton instance2 = (HungrySingleton) ois.readObject();
ois.close();
ois.close();
System.out.println(instance1 == instance2);
}
}
追踪readObject方法源码发现当序列化对象实现readResolve方法时会将序列化后的对象使用readResolve中return对象代替
if (obj != null && handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod()){
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
handles.setObject(passHandle, obj = rep);
}
}
所以解决方案如下
private Object readResolve(){
return INSTANCE;
}
所以最终单例实现为
public class HungrySingleton implements Serializable {
private static final HungrySingleton INSTANCE = new HungrySingleton();
private HungrySingleton(){
if(INSTANCE != null){
throw new RuntimeException("该类不能创建多个实例");
}
}
private Object readResolve(){
return INSTANCE;
}
public static HungrySingleton getInstance(){
return INSTANCE;
}
}
枚举类型防止反射破坏单例因为枚举没有明确构造函数,也可以防止序列化,所以枚举是实现单例最优的方式
单例模式不能进行扩展,不符合开闭原则