• GOF23 单例模式


    单例模式

    优点

    1. 单例模式可以保证内存中只有一个实例,减少了内存的开销
    2. 可以避免对资源的多重占用
    3. 单例模式设置全局访问点,可以优化和共享资源的访问

    缺点

    1. 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则
    2. 在并发测试中,单例模式不利于代码调试。在调试的过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象
    3. 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责。

    饿汉式单例

    缺点

    当存在大量的单例对象的时候,而且单例的数量不能确定,则系统初始化过程中会造成资源浪费,从而导致系统内存不足

    // 饿汉式单例
    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);
        }
    
    }
    

    image-20201129100324280

    继续再次破坏单例模式

    image-20201129100841857

    image-20201129100948231

    道高一尺 魔高

    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();
            }
        }
    }
    
    • 结果

    image-20210321141120020

    • 这根我们预期的结果不太一样
    // 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()方法。

    image-20210321143811843

    • 接下来我们看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);
        }
    }
    
    • 显示没有无参构造方法 而不是显示不能通过反射调用

    image-20201129221127585

    再次通过反射破解

    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);
        }
    }
    

    image-20201129220930974

    查看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();
            }
        }
    }
    
    • 结果如图所示 发现并不是同一个对象实例

    image-20210321145526747

    • 那么如何通过序列化实现单例模式呢 其实很简单 添加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;
        }
    }    
    
    • 再次运行:

    image-20210321145732075

  • 相关阅读:
    POJ 2329 Nearest number
    POJ 2192 Zipper (简单DP)
    POJ 2231 Moo Volume(递推、前缀和)
    数据库增删改查--2017-04-08
    时间戳--2017-04-07
    数据库三大范式---2017-04-07
    数据库主键和外键----数据库基础知识1---2017-04-07
    登录页面(简单版,带遮罩层)---2017-04-06 (与04-05日写的差不多,界面圆滑点)
    第一阶段项目所遇到的问题---2017-04-066
    图片点击轮播(四)高级--2017-04-05
  • 原文地址:https://www.cnblogs.com/zgrey/p/14563047.html
Copyright © 2020-2023  润新知