单例模式
优点
- 单例模式可以保证内存中只有一个实例,减少了内存的开销
- 可以避免对资源的多重占用
- 单例模式设置全局访问点,可以优化和共享资源的访问
缺点
- 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则
- 在并发测试中,单例模式不利于代码调试。在调试的过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象
- 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责。
饿汉式单例
缺点
当存在大量的单例对象的时候,而且单例的数量不能确定,则系统初始化过程中会造成资源浪费,从而导致系统内存不足
// 饿汉式单例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); //设置字段的值为falseLazyMan 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.javapackage 521B5EFA578B6A215F0F.53554F8B6A215F0F;import java.io.Serializable;public final class EnumSingleton extends Enumimplements 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
源码
@CallerSensitivepublic 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 volatileif (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.javapackage 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;}}
- 再次运行: