GOF23 单例模式
2021-03-21 15:25
771 查看
单例模式
优点
- 单例模式可以保证内存中只有一个实例,减少了内存的开销
- 可以避免对资源的多重占用
- 单例模式设置全局访问点,可以优化和共享资源的访问
缺点
- 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则
- 在并发测试中,单例模式不利于代码调试。在调试的过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象
- 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责。
饿汉式单例
缺点
当存在大量的单例对象的时候,而且单例的数量不能确定,则系统初始化过程中会造成资源浪费,从而导致系统内存不足
// 饿汉式单例 public class Hungry { // 可能会浪费空间 private byte[] data1 = new byte[1024 * 1024]; private byte[] data2 = new byte[1024 * 1024]; private byte[] data3 = new byte[1024 * 1024]; private byte[] data4 = new byte[1024 * 1024]; private Hungry() { } private final static Hungry HUNGRY = new Hungry(); public static Hungry getInstance() { return HUNGRY; } }
懒汉式单例 DCL
双重检查锁单例
public class LazyMan { private volatile static LazyMan lazyMan; //禁止指令重排 private LazyMan(){ System.out.println(Thread.currentThread().getName()); } public static LazyMan getInstance(){ // 双重检测锁模式的 懒汉式单例 DCL懒汉式 if (lazyMan==null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); //不是一个原子性操作 } } } return lazyMan; } public static void main(String[] args) { //测试是否为单例对象 for (int i = 0; i <10 ; i++) { new Thread(()->{ getInstance(); }).start(); } } }
通过反射初步破坏单例模式
public class LazyMan { private volatile static LazyMan lazyMan; //禁止指令重排 private LazyMan(){ //加锁判断lazyMan是否存在 存在的话抛出异常 synchronized(LazyMan.class){ if (lazyMan!=null){ throw new RuntimeException("不要通过反射破坏"); } } } public static LazyMan getInstance(){ // 双重检测锁模式的 懒汉式单例 DCL懒汉式 if (lazyMan==null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); //不是一个原子性操作 } } } return lazyMan; } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null); constructor.setAccessible(true); LazyMan lazyMan = getInstance(); LazyMan lazyMan1 = constructor.newInstance(); System.out.println(lazyMan); System.out.println(lazyMan1); } }
继续再次破坏单例模式
道高一尺 魔高
public class LazyMan { private volatile static LazyMan lazyMan; //禁止指令重排 private volatile static boolean zjh=false; // 添加一个判断字段 private LazyMan(){ synchronized(LazyMan.class){ if (!zjh){ zjh=true; }else{ throw new RuntimeException("不要试图通过反射破坏单例模式"); } } } public static LazyMan getInstance(){ // 双重检测锁模式的 懒汉式单例 DCL懒汉式 if (lazyMan==null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); //不是一个原子性操作 } } } return lazyMan; } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null); Field zjh = LazyMan.class.getDeclaredField("zjh"); //获取zjh字段 constructor.setAccessible(true); //强制 // LazyMan lazyMan = getInstance(); zjh.setAccessible(true); LazyMan lazyMan1 = constructor.newInstance(); zjh.set(lazyMan1,false); //设置字段的值为false LazyMan lazyMan = constructor.newInstance(); System.out.println(lazyMan); System.out.println(lazyMan1); } } / ** * 1. 分配内存空间 * 2、执行构造方法,初始化对象 * 3、把这个对象指向这个空间 * * 123 * 132 A * B // 此时lazyMan还没有完成构造 */
静态内部类
public class Holder { private Holder(){ } public static Holder getInstance(){ return InnerClass.HOLDER; } public static class InnerClass{ private static final Holder HOLDER=new Holder(); } }
- 单例不安全,可以通过反射破解
枚举
枚举式单例写法
public enum EnumSingleton implements Serializable { INSTALL; private Object data; public Object getData() { return data; } public void setData(Object data) { this.data = data; } public static EnumSingleton getInstance(){ return INSTALL; } }
- 测试
public class EnumSingletonTest { public static void main(String[] args) { try{ EnumSingleton instance1=null; EnumSingleton instance2=EnumSingleton.getInstance(); instance2.setData(new Object()); FileOutputStream fileOutputStream=new FileOutputStream("EnumSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fileOutputStream); oos.writeObject(instance2); oos.flush(); oos.close(); FileInputStream fileInputStream=new FileInputStream("EnumSingleton.obj"); ObjectInputStream oos2 = new ObjectInputStream(fileInputStream); instance1= (EnumSingleton) oos2.readObject(); oos2.close(); System.out.println(instance1.getData()); System.out.println(instance2.getData()); System.out.println(instance1.getData()==instance2.getData()); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } }
- 结果
- 这根我们预期的结果不太一样
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) // Source File Name: EnumSingleton.java package 521B5EFA578B6A215F0F.53554F8B6A215F0F; import java.io.Serializable; public final class EnumSingleton extends Enum implements Serializable { public static EnumSingleton[] values() { return (EnumSingleton[])$VALUES.clone(); } //toString的逆方法,返回指定名字,给定类的枚举常量 public static EnumSingleton valueOf(String name) { return (EnumSingleton)Enum.valueOf(521B5EFA578B6A215F0F/53554F8B6A215F0F/EnumSingleton, name); } //私有构造函数,参数有 此枚举常量的名称,枚举常量的序号 private EnumSingleton(String s, int i) { super(s, i); } public Object getData() { return data; } public void setData(Object data) { this.data = data; } public static EnumSingleton getInstance() { return INSTALL; } //单例对象的名称 public static final EnumSingleton INSTALL; //单例对象的属性 private Object data; //枚举类中的所有值 包装到values数组中 private static final EnumSingleton $VALUES[]; static { //与饿汉式相似,类初始化时创建单例对象 指定名称和枚举常量的序号 INSTALL = new EnumSingleton("INSTALL", 0); $VALUES = (new EnumSingleton[] { INSTALL }); } }
枚举式单例写法在静态块中就对INSTALL赋值了,是饿汉式的实现
- 至此我们还可以想序列化是否可以破解枚举的单例 ,现在回到
ObjectInputStream的readObject0()
方法。
- 接下来我们看
readEnum
的代码实现
/** * Reads in and returns enum constant, or null if enum type is * unresolvable. Sets passHandle to enum constant's assigned handle. */ private Enum<?> readEnum(boolean unshared) throws IOException { if (bin.readByte() != TC_ENUM) { throw new InternalError(); } ObjectStreamClass desc = readClassDesc(false); if (!desc.isEnum()) { throw new InvalidClassException("non-enum class: " + desc); } int enumHandle = handles.assign(unshared ? unsharedMarker : null); ClassNotFoundException resolveEx = desc.getResolveException(); if (resolveEx != null) { handles.markException(enumHandle, resolveEx); } String name = readString(false); Enum<?> result = null; Class<?> cl = desc.forClass(); if (cl != null) { try { @SuppressWarnings("unchecked") Enum<?> en = Enum.valueOf((Class)cl, name); result = en; } catch (IllegalArgumentException ex) { throw (IOException) new InvalidObjectException( "enum constant " + name + " does not exist in " + cl).initCause(ex); } if (!unshared) { handles.setObject(enumHandle, result); } } handles.finish(enumHandle); passHandle = enumHandle; return result; }
由上可知,枚举类型其实通过类名和类对象找到唯一的枚举对象。因此,枚举对象不可能被类加载器加载两次。
试图通过反射破坏枚举的单例模式
public enum EnumSingle { INSTANCE; private EnumSingle getInstance(){ return INSTANCE; } } class Test01{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); EnumSingle enumSingle = declaredConstructor.newInstance(); System.out.println(enumSingle); } }
- 显示没有无参构造方法 而不是显示不能通过反射调用
再次通过反射破解
public enum EnumSingle { INSTANCE; private EnumSingle getInstance(){ return INSTANCE; } } class Test01{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); EnumSingle enumSingle = declaredConstructor.newInstance(); System.out.println(enumSingle); } }
查看
newInstance源码
@CallerSensitive public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class<?> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, null, modifiers); } } //判断是否为枚举类 if ((clazz.getModifiers() & Modifier.ENUM) != 0) //如果是直接抛出异常 throw new IllegalArgumentException("Cannot reflectively create enum objects"); ConstructorAccessor ca = constructorAccessor; // read volatile if (ca == null) { ca = acquireConstructorAccessor(); } @SuppressWarnings("unchecked") T inst = (T) ca.newInstance(initargs); return inst; }
反编译源码
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) // Source File Name: EnumSingle.java package com.itheim.Demo14; public final class EnumSingle extends Enum { public static EnumSingle[] values() { return (EnumSingle[])$VALUES.clone(); } public static EnumSingle valueOf(String name) { return (EnumSingle)Enum.valueOf(com/itheim/Demo14/EnumSingle, name); } private EnumSingle(String s, int i) { super(s, i); } public EnumSingle getInstance() { return INSTANCE; } public static final EnumSingle INSTANCE; private static final EnumSingle $VALUES[]; static { INSTANCE = new EnumSingle("INSTANCE", 0); $VALUES = (new EnumSingle[] { INSTANCE }); } }
还原反序列化破坏单例模式的事故现场
- 序列化就是把内存中的状态通过转换为字节码的形式 从而转换为IO流,写入其他的地方(可以是磁盘、网络IO)内存中的状态会被永久保留下来
- 反序列化就是把已经序列化的字节码内容转换为IO流 通过IO流的读取,进而将读取的内容转换为
java
对象在转换过程中重新创建对象
package 创建型模式.单例模式; import java.io.*; /** * @program: DesignPattern * @description: * @author: ZGrey * @create: 2021-03-21 13:53 **/ 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; } public static void main(String[] args) { try{ SeriableSingleton instance1=null; SeriableSingleton instance2=SeriableSingleton.getInstance(); FileOutputStream fileOutputStream=new FileOutputStream("EnumSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fileOutputStream); oos.writeObject(instance2); oos.flush(); oos.close(); FileInputStream fileInputStream=new FileInputStream("EnumSingleton.obj"); ObjectInputStream oos2 = new ObjectInputStream(fileInputStream); instance1= (SeriableSingleton) oos2.readObject(); oos2.close(); System.out.println(instance1); System.out.println(instance2); System.out.println(instance1==instance2); } catch (IOException | ClassNotFoundException 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; } }
- 再次运行:
相关文章推荐
- 【GOF23设计模式】_原型模式JAVA233-234
- GOF23设计模式之责任链模式(chain of responsibility)之实现
- GOF 23设计模式之 装饰模式(Decorator)
- 【GOF23设计模式】_代理模式_静态代理_动态代理_开发场景JAVA236-237
- GOF23之单例模式详解
- [转]笔记--设计模式精解c++-GoF 23 种设计模式解析
- GOF23之解释器模式
- GOF23之工厂模式
- 设计模式GOF23——组合模式
- 设计模式GOF23——责任链模式
- 【GOF23设计模式】_观察者模式
- GOF23 工厂模式
- 【GOF23设计模式】代理模式
- 【GOF23设计模式】备忘录模式
- 设计模式GOF23——备忘录模式
- 226_尚学堂_高淇_java300集最全视频教程_【GOF23设计模式】_单例模式_应用场景_饿汉式_懒汉式
- GOF23设计模式之 迭代器模式
- GOF23设计模式之观察者模式之理解
- GOF23之模板方法模式
- java GOF23设计模式-简单工厂模式进阶