• 详解单例模式


    单例模式

    1.饿汉式单例

    特点:一上来先实例化对象

    存在问题:可能会造成资源空间的浪费

    /**
     * @Description: 饿汉式单例模式
     * @Author:啵啵啵啵啵啵唧~~~
     * @Date:2022/6/29
     */
    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;
        }
        
    }
    
    • 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

    2.懒汉式单例

    //---------------------基础版本单例模式,存在线程安全问题----------------------
    public class LazyMan {
    
        /**
         * 私有构造
         */
        private LazyMan() {
            
        }
    
        /**
         * 上来先不实例化对象
         */
        private static LazyMan lazyMan;
    
        /**
         * 检测以下对象是否为空,当为空的时候才创建
         * @return
         */
        public static LazyMan getInstance() {
            if (lazyMan == null) {
                lazyMan = new LazyMan();
            }
            return lazyMan;
        }
    }
    //-----------------------双重检测锁模式版本-----------------------------
    public class LazyMan {
    
        /**
         * 私有构造
         */
        private LazyMan() {
            
        }
    
        /**
         * 双重检测锁对象我们给定为volatile进行修饰,防止指令重排,因为:
         * lazyMan = new LazyMan(); 这个new对象的操作在底层并不是原子性的
         * 1. 分配内存空间
         * 2. 执行构造方法,初始化对象
         * 3. 把这个对象指向这个空间
         * 假设现在A线程执行创建实例的代码,底层发生了指令重排 创建对象的三步顺序为 1 3 2 
         * 意味着线程A还没有初始化对象就指向了空间,假设此时有一个线程B执行到了这里
         * 线程B在判断第一个lazyMan==null的时候就为false,因为线程A已经指向了内存空间
         * 那么此时线程B就直接返回了这个对象,但是实际上线程A还没有初始化这个对象,所以返回的这个对象是虚无的
         * 所以为了创建对象的时候发生指令重排,加一个volatile关键字修饰
         */
        private volatile static LazyMan lazyMan;
    
        /**
         * 检测以下对象是否为空,当为空的时候才创建
         * @return
         */
        public static LazyMan getInstance() {
            //这个第一个lazyMan == null 的作用是为了提高效率,不加这一层的话,每一次都要获得锁,获得锁实际上是很拉效率的操作
            if (lazyMan == null) {
                synchronized (LazyMan.class) {
                    if (lazyMan == null) {
                        lazyMan = new LazyMan();
                    }
                }
            }
            return lazyMan;
        }
    }
    
    • 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
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    3.静态内部类获取单例

    public class Holder {
        /**
         * 构造器私有
         */
        private Holder() {
    
        }
    
        public static Holder getInstance() {
            return InnerClass.HOLDER;
        }
        /**
         * 静态内部类创建实例
         */
        public static class InnerClass{
            private static final Holder HOLDER = new Holder();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    4.反射破环单例模式

    正常获取

        public static void main(String[] args) {
            LazyMan instance1 = LazyMan.getInstance();
            LazyMan instance2 = LazyMan.getInstance();
            System.out.println(instance1.hashCode());
            System.out.println(instance2.hashCode());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    破环一:一个采用正常方式获取单例,一个采用反射的方式获取单例

        public static void main(String[] args) throws Exception {
            LazyMan instance1 = LazyMan.getInstance();
            //获取单例类的构造器
            Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
            //无视私有的构造器
            declaredConstructor.setAccessible(true);
            //通过反射的方式获取实例
            LazyMan instance2 = declaredConstructor.newInstance();
            System.out.println(instance1.hashCode());
            System.out.println(instance2.hashCode());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rdP5yLgq-1656521562320)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/image-20220630000533306.png)]

    • 解决方法,改善私有构造器,三重检测锁的方式,我们在单例类的构造方法当中判断一下,instance1是否为空,如果不为空抛出异常
        /**
         * 私有构造
         */
        private LazyMan() {
            synchronized (LazyMan.class) {
                if (lazyMan != null) {
                    //不为空还调用了构造器,说明被反射破坏了,抛出一个异常阻止
                   throw new RuntimeException("不要试图使用反射破环单例模式!!!");
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qr4TjQgv-1656521562321)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/image-20220630001048394.png)]

    破环二,不使用类中提供的引用创建对象,每次创建对象都使用反射的方法

        public static void main(String[] args) throws Exception {
            //获取单例类的构造器
            Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
            //无视私有的构造器
            declaredConstructor.setAccessible(true);
            //通过反射的方式获取实例
            LazyMan instance1 = declaredConstructor.newInstance();
            LazyMan instance2 = declaredConstructor.newInstance();
            System.out.println(instance1.hashCode());
            System.out.println(instance2.hashCode());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zRs6OtZs-1656521562322)(C:/Users/zhengbo/%E6%88%91%E7%9A%84%E5%AD%A6%E4%B9%A0/Typora%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/image-20220630002101367.png)]

    • 这种情况的解决办法,可以通过做一个打标记的方式解决
        /**
         * 红绿灯策略,防止单例杯破坏
         */
        private static boolean target = false;
    
        /**
         * 私有构造
         */
        private LazyMan() {
            if (!target) {
                target = true;
            } else {
                throw new RuntimeException("不要试图通过反射的方式破环单例");
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    破环三,通过破环类中的字段,破坏红绿灯策略

        public static void main(String[] args) throws Exception {
            //获取单例类的构造器
            Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
            //无视私有的构造器
            declaredConstructor.setAccessible(true);
            //获取单例类中的target字段
            Field target = LazyMan.class.getDeclaredField("target");
            target.setAccessible(true);
            //通过反射的方式获取实例
            LazyMan instance1 = declaredConstructor.newInstance();
    
            //执行完毕之后,我们将字段的值改为false
            target.set(instance1,false);
    
            LazyMan instance2 = declaredConstructor.newInstance();
            System.out.println(instance1.hashCode());
            System.out.println(instance2.hashCode());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述

    道高一尺魔高一丈!!!

    5.枚举的方式,防止单例被破坏

    /**
     * @Description: 枚举的方式防止单例被破坏
     * enum本身也是一个类
     * @Author:啵啵啵啵啵啵唧~~~
     * @Date:2022/6/30
     */
    public enum EnumSingle {
        INSTANCE;
        public EnumSingle getInstance() {
            return INSTANCE;
        }
    
    
    }
    class Test {
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            EnumSingle instance1 = EnumSingle.INSTANCE;
            Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
            declaredConstructor.setAccessible(true);
            EnumSingle instance2 = declaredConstructor.newInstance();
    
            System.out.println(instance1);
            System.out.println(instance2);
        }
    }
    
    • 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
    • 枚举类型最终的反编译后是有参构造器
    • 反射破坏枚举抛出异常

    在这里插入图片描述

  • 相关阅读:
    安卓实现饿了么点餐界面效果(京东类别左右列表联动)
    学生用台灯什么光对眼睛好?分享暖白光的学生护眼台灯
    【Mysql】数据库第三讲(表的约束、基本查询语句)
    稳压二极管的应用及注意事项
    代码随想录算法训练营第二十八天| 509. 斐波那契数 , 70. 爬楼梯 , 746. 使用最小花费爬楼梯
    【计算机毕业设计】企业员工岗前培训管理系统
    离线数据仓库第二讲
    VideoPlayerWithOpenCVForUnityExample
    Spring Security JWT 添加额外信息
    刷题笔记之十 (小易的升级之路+找出字符串中第一个只出现一次的字符+洗牌+MP3光标位置)
  • 原文地址:https://blog.csdn.net/weixin_45809829/article/details/125532327