zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    算法----(1)冒泡排序
    淘宝爬虫
    爬虫_豆瓣电影top250 (正则表达式)
    爬虫_猫眼电影top100(正则表达式)
    Android 简单调用摄像头
    Android 简单天气预报
    思维模型
    This view is not constrained, it only has designtime positions, so it will jump to (0,0) unless you
    Android studio preview界面无法预览,报错render problem
    Android studio 3.1.2报错,no target device found
  • 原文地址:https://www.cnblogs.com/zgrey/p/14563047.html
Copyright © 2011-2022 走看看