• 单例模式深入


    前言:今天公司让我搭建设计模式的课程,结果在单例模式被教训了一波,发现有很多都没有深入

    反射如何破坏单例模式

    1.单例类:

    package 单例模式;
     
    /**
     * @author diao 2022/3/10
     */
    public class Singleton {
        private Singleton(){
            
        }
        private static Singleton singleton=null;
        //静态工厂方法
        public static Singleton getInstance(){
            if(singleton==null){
                singleton=new Singleton();
            }
            return singleton;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.反射破坏单例模式:
    本质利用类的构造方法得到类的实例

    package 单例模式;
     
    /**
     * @author diao 2022/3/10
     */
    public class Singleton {
        private Singleton(){
            
        }
        private static Singleton singleton=null;
        //静态工厂方法
        public static Singleton getInstance(){
            if(singleton==null){
                singleton=new Singleton();
            }
            return singleton;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    结果:
    671631440 935563443
    发现是不同的实例——>原因:反射获得单例类的构造函数,使得单例类失效

    3.如何预防?
    因为我们反射它是调用了零次构造函数所以我们只需要防止连续被调用两次构造函数即可,需要进行次数统计(多线程下需要保证次数count不安全的情况)
    在构造函数里面进行上锁判断count如果>0说明前面有人来过,抛出异常即可,否则count++;

    public class Singleton {
        private static int count = 0;
     
        private static Singleton instance = null;
     
        private Singleton(){
            synchronized (Singleton.class) {
                if(count > 0){
                    throw new RuntimeException("创建了两个实例");
                }
                count++;
            }
     
        }
     
        public static Singleton getInstance() {
            if(instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
     
        public static void main(String[] args) throws Exception {
     
            Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
            constructor.setAccessible(true);
            Singleton s1 = constructor.newInstance();
            Singleton s2 = constructor.newInstance();
        }
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    结果抛出异常

    反序列化破坏单例模式

    1.单例类
    这里采用的是懒汉式获取我们的实例,并且定义count判断来防止反射破坏单例问题,并且懒汉的get采用了双重检测防止多线程造成并发导致创建多个不同的实例

    /**
     * 双重检测锁的单例模式
     */
    class DoubleIfSynchronizedSingleton implements Serializable {
        private static final long serialVersionUID = 3288137447362611668L;
        private static int count = 0;  //创建对象次数
        private volatile static DoubleIfSynchronizedSingleton singleton = null;
     
        private DoubleIfSynchronizedSingleton() {
            //解决反射破坏单例问题
            synchronized (DoubleIfSynchronizedSingleton.class) {
                if (count > 0){
                    throw new RuntimeException("你居然敢破坏我的单例.....");
                }
                count++;
            }
        }
     
        /**
         * 单例对象生成方法
         * @return
         */
        public static DoubleIfSynchronizedSingleton getSingleton() {
            if (singleton == null) {
                synchronized (DoubleIfSynchronizedSingleton.class) {
                    if (singleton == null) {
                        singleton = new DoubleIfSynchronizedSingleton();
                    }
                }
            }
            return singleton;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    2.反序列化破坏单例模式

    
    /**
     * 反序列化破坏单例模式
     */
    public class DeserializedBreakSingleton {
     
        @Test
        public void test(){
            //通过单例模式获取对象
            DoubleIfSynchronizedSingleton singleton = DoubleIfSynchronizedSingleton.getSingleton();
            //通过反序列化获取对象
            DoubleIfSynchronizedSingleton deserializedSingleton = DeserializedBreakSingleton.getSerializedSingleton(singleton);
            System.out.println("hashCode of singleton:"+singleton);
            System.out.println("hashCode of deserializedSingleton:"+deserializedSingleton);
            System.out.println(singleton == deserializedSingleton);
        }
     
        /**
         * 通过序列化获取单例对象
         */
        private static DoubleIfSynchronizedSingleton getSerializedSingleton(DoubleIfSynchronizedSingleton singleton){
            File file = new File("singleton");
            ObjectOutputStream oos = null;
            ObjectInputStream ois = null;
            DoubleIfSynchronizedSingleton newSingleton = null;
            try {
                oos = new ObjectOutputStream(new FileOutputStream(file));
                oos.writeObject(singleton);
                oos.flush();
                ois = new ObjectInputStream(new FileInputStream(file));
                newSingleton = (DoubleIfSynchronizedSingleton) ois.readObject();
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }finally {
                if (ois != null){
                    try {
                        ois.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (oos != null){
                    try {
                        oos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if(file.exists()){
                    file.delete();
                }
            }
            return newSingleton;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    结果:
    hashCode of singleton:serialization.DoubleIfSynchronizedSingleton@1376c05c hashCode of deserializedSingleton:serialization.DoubleIfSynchronizedSingleton@e2d56bf false

    3.解决
    我们在单例类中加上readResolve()方法即可
    原因: readObject()方法回通过一个三元运算来判断创建序列化的对象,如果能实例化就创建一个新的对象,但是单例类肯定不是能够实例化的,但是readObject()方法中还会判断是否有readResolve,如果有的话就根据这个方法返回对象,所以反序列化回来的对象是一个新的对象而不是之前单例那个对象;

    /**
     * 双重检测锁的单例模式
     */
    class DoubleIfSynchronizedSingleton implements Serializable {
        private static final long serialVersionUID = 3288137447362611668L;
        private static int count = 0;  //创建对象次数
        private volatile static DoubleIfSynchronizedSingleton singleton = null;
     
        private DoubleIfSynchronizedSingleton() {
            //解决反射破坏单例问题
            synchronized (DoubleIfSynchronizedSingleton.class) {
                if (count > 0){
                    throw new RuntimeException("你居然敢破坏我的单例.....");
                }
                count++;
            }
        }
     
        /**
         * 单例对象生成方法
         * @return
         */
        public static DoubleIfSynchronizedSingleton getSingleton() {
            if (singleton == null) {
                synchronized (DoubleIfSynchronizedSingleton.class) {
                    if (singleton == null) {
                        singleton = new DoubleIfSynchronizedSingleton();
                    }
                }
            }
            return singleton;
        }
     
        /**
         * 解决反序列化破坏单例问题
         */
        private Object readResolve(){
            return singleton;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    枚举实现单例类

    1.单例类:
    在实现过程中,Java虚拟机会保证枚举类型不能被反射并且构造函数只被执行一次。

    public class Singleton {
        private Singleton(){
        }   
        public static enum SingletonEnum {
            SINGLETON;
            private Singleton instance = null;
            private SingletonEnum(){
                instance = new Singleton();
            }
            public Singleton getInstance(){
                return instance;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.测试类:
    单例模式的枚举实现代码比较简单,而且又可以利用枚举的特性来解决线程安全和单一实例的问题,还可以防止反射和反序列化对单例的破坏,因此在很多书和文章中都强烈推荐将该方法作为单例模式的最佳实现方法

        ……	
        public static void main(String args[]) {
        	Singleton s1 = SingletonEnum.SINGLETON.getInstance();
        	Singleton s2 = SingletonEnum.SINGLETON.getInstance();
        	System.out.println(s1==s2);
        }
        ……
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • 相关阅读:
    Python爬虫|Scrapy 基础用法
    websocket协议
    width:100%和width:auto有啥区别
    手把手教你写一个SpringMVC框架
    接口自动化测试思路和实战(1):编写线性测试脚本实战
    c++ vector erase
    Elasticsearch:从 Java High Level Rest Client 切换到新的 Java API Client
    prometheus学习2数据类型了解&PromQL
    信息学奥赛中的STL(标准模板库)--2022.09.30
    数据结构之栈
  • 原文地址:https://blog.csdn.net/weixin_57128596/article/details/128193434