单例模式-2(注册式单例)
引言
序列化破坏单例:一个单例对象创建好后,有时候需要将对象序列化后写入磁盘,下次使用时再从磁盘中读取对象并进行反序列化,将其转化为内存对象。反序列化后的对象将会重新分配内存,即重新创建。如果序列化的目标对象为单例对象,就违背了单例模式的初衷,相当于破坏了单例,看如下代码。
public class SeriableSingleton implements Serializable { public final static SeriableSingleton INSTANCE = new SeriableSingleton(); private SeriableSingleton(){} public static SeriableSingleton getInstance(){ return INSTANCE; } }
测试代码如下
@Test void SeriableSingletonTest(){ SeriableSingleton s1 = null; SeriableSingleton s2 = SeriableSingleton.getInstance(); FileOutputStream fos = null; try { fos = new FileOutputStream("SeriableSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("SeriableSingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); s1 = (SeriableSingleton)ois.readObject(); ois.close(); System.out.println(s1); System.out.println(s2); System.out.println(s1 == s2); } catch (Exception e) { e.printStackTrace(); } }
运行结果如下图
从运行结果可以看出,反序列化后的对象和手动创建的对象是不一致的,实例化了两次,违背了单例模式的设计初衷。那么如何保证在序列化的情况下也能实现单例模式呢?只需要增加 readResolve() 方法即可。
public class SeriableSingleton implements Serializable { public final static SeriableSingleton INSTANCE = new SeriableSingleton(); private SeriableSingleton(){} public static SeriableSingleton getInstance(){ return INSTANCE; } private Object readResolve(){ return INSTANCE; } }
运行结果如下图:
读JDK 的 源码发现,虽然增加 readResolve() 方法返回实例解决了单例模式被破坏的问题,但是实际上实例化了两次,只不过新创建的对象没有被返回而已。如果创建对象的动作发生频率加快,就意味着内存分配的开销也会随之增大,这时候就注册式单例就可以登场了。
注册式单例模式
注册式单例模式又称为等级式单例模式,就是将每一个实例都登记到某一个地方,使用唯一的标识获取实例。注册式单例i模式有两种:一种是枚举式单例模式,另一种是容器式单例模式。
1.枚举式单例模式
枚举式单例模式写法如下:
public enum EnumSingleton { INSTANCE; private Object data; public Object getData() { return data; } public void setData(Object data) { this.data = data; } public static EnumSingleton getInstance(){ return INSTANCE; } }
测试代码如下:
@Test void EnumSingletonTest(){ EnumSingleton e1 = null; EnumSingleton e2 = EnumSingleton.getInstance(); e2.setData(new Object()); FileOutputStream fos = null; try { fos = new FileOutputStream("EnumSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(e2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("EnumSingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); e1 = (EnumSingleton)ois.readObject(); ois.close(); System.out.println(e1.getData()); System.out.println(e2.getData()); System.out.println(e1.getData() == e2.getData()); } catch (Exception e) { e.printStackTrace(); } }
运行结果如下:
枚举类型其实通过类名和类对象找到一个唯一的枚举对象。因此,枚举对象不可能别类加载器加载多次。
那么反射能否破坏枚举式单例模式呢?测试代码如下:
@Test void EnumSingletonTestThread() { try{ Class clazz = EnumSingleton.class; Constructor c = clazz.getDeclaredConstructor(); c.newInstance(); } catch (Exception e){ e.printStackTrace(); } }
运行结果如下:
报错没有找到无参构造方法。查看 Enum 源码 他的构造方法只有一个 protected 类型的构造方法,代码如下:
protected Enum(String name, int ordinal){ this.name = name; this.ordinal = ordinal; }
再做如下测试:
@Test void EnumSingletonTestThread() { try{ Class clazz = EnumSingleton.class; Constructor c = clazz.getDeclaredConstructor(String.class,int.class); c.setAccessible(true); EnumSingleton enumSingleton = (EnumSingleton)c.newInstance("l-coil",666); } catch (Exception e){ e.printStackTrace(); } }
测试结果如下:
报错已经很明显了,不能用反射来创建枚举类型。
枚举式单例模式也是 Effective Java 书中推荐的一种单例模式实现写法。JDK 枚举的语法特殊性质及繁殖也为枚举报价护航,让枚举式单例模式成为一种比较优雅的实现。
2.容器式单例
容器式单例写法如下:
public class ContainerSingleton { private ContainerSingleton(){} private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>(); public static Object getBean(String className){ synchronized (ioc){ if(!ioc.containsKey(className)){ Object obj = null; try{ obj = Class.forName(className).newInstance(); ioc.put(className, obj); }catch (Exception e){ e.printStackTrace(); } return obj; } else { return ioc.get(className); } } } }
容器式单例模式使用与实例非常多的情况,编辑管理。单它是非线程安全的。
本文来自博客园,作者:l-coil,转载请注明原文链接:https://www.cnblogs.com/l-coil/p/12863405.html