• 23 种设计模式之单例模式


    23 种设计模式之单例模式



    一、认识

    ①一句话来说就是,某个类只能有一个实例,提供一个全局的访问点。

    ②单例模式的要点有三个:

    • 一是某个类只能有一个实例;
    • 二是它必须自行创建这个实例;
    • 三是它必须自行向整个系统提供这个实例。

    ③使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(garbage collection),而且确保所有对象都访问唯一实例。但是不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。用单例模式,就是在适用其优点的状态下使用

    回到顶部


    二、UML类图

    在这里插入图片描述
    UML说明:

    • 1.构造方法私有化:可以使得该类不被实例化即不能被new
    • 2.在类本身里创建自己的对象
    • 3.提供一个公共的方法供其他对象访问

    回到顶部


    三、代码实现

    1. 饿汉式

    第一种:

    public class Singleton {
    
        /**
         * static:
         * ①表示共享变量,语意符合
         * ②使得该变量能在getInstance()静态方法中使用
         * final:
         * ①final修饰的变量值不会改变即常量,语意也符合,当然不加final也是可以的
         * ②保证修饰的变量必须在类加载完成时就已经进行赋值。
         * final修饰的变量,前面一般加static
         */
        private static final Singleton singleton = new Singleton();
    
        /**
         * 私有化构造方法,使外部无法通过构造方法构造除singleton外的类实例
         * 从而达到单例模式控制类实例数目的目的
         */
        private Singleton(){}
    
        /**
         * 类实例的全局访问方法
         * 因为构造方法以及被私有化,外部不可能通过new对象来调用其中的方法
         * 加上static关键词使得外部可以通过类名直接调用该方法获取类实例
         * @return
         */
        public static Singleton getSingleton() {
            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

    第二种

    public class SingletonStatic {
    
        private static final SingletonStatic singletonStatic;
    
        /**
         * 和第一种没有什么区别,这种看起来高大上面试装逼使用
         */
        static {
            singletonStatic = new SingletonStatic();
        }
    
        private SingletonStatic() {}
    
        public static SingletonStatic getSingletonStatic(){
            return singletonStatic;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    说明:

    • 优点: 一般使用static和final修饰变量(具体作用已经在代码里描述了),只在类加载时才会初始化,以后都不会,线程绝对安全,无锁,效率高。
    • 缺点: 类加载的时候就初始化,不管用不用,都占用空间,会消耗一定的性能(当然很小很小,几乎可以忽略不计,所以这种模式在很多场合十分常用而且十分简单)

    注: 这里有两个小知识点:

    • 如果是final非static成员,必须在构造器、代码块、或者直接定义赋值
    • 如果是final static 成员变量,必须直接赋值 或者在静态代码块中赋值

    2. 懒汉式

    public class Singleton {
    
        private static Singleton singleton = null;
    
        private Singleton(){}
    
        public static Singleton getSingleton() {
            if(singleton == null){
                singleton = new Singleton();
            }
            return singleton;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    说明:

    • 优点: 在外部需要使用的时候才进行实例化,不使用的时候不会占用空间。
    • 缺点: 线程不安全。看上去,这段代码没什么明显问题,但它不是线程安全的。假设当前有N个线程同时调用getInstance()方法,由于当前还没有对象生成,所以一部分同时都进入if语句new
      Singleton(),那么就会由多个线程创建多个user对象。

    3. 线程安全的懒汉式

    public class Singleton {
    
        private static Singleton singleton;
    
        private Singleton(){};
    
        private static synchronized Singleton getSingleton(){
            if(singleton == null){
                singleton = new Singleton();
            }
            return singleton;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    说明:

    • 优点: 解决了懒汉式线程不安全的问题
    • 缺点: 线程阻塞,影响性能。

    4. DCL单例 - 高性能的懒汉式

    public class Singleton {
     /*volatile在这里发挥的作用是:禁止指令重排序(编译器和处理器为了优化程序性能
        * 而对指令序列进行排序的一种手段。)
        * singleton = new Singleton();这句代码是非原子性操作可分为三行伪代码
        * a:memory = allocate() //分配内存,在jvm堆中分配一段区域
        * b:ctorInstanc(memory) //初始化对象,在jvm堆中的内存中实例化对象
        * c:instance = memory //赋值,设置instance指向刚分配的内存地址
        * 上面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。
        * 重排序是为了优化性能,但是不管怎么重排序,在单线程下程序的执行结果不能被改变
        * 保证最终一致性。而在多线程环境下,可能发生重排序,会影响结果。
        * ①若A线程执行到代码singleton = new Singleton()时;
        * ②同时若B线程进来执行到代码到第一层检查if (singleton == null)
        * ③当cpu切换到A线程执行代码singleton = new Singleton();时发生了指令重排序,
        * 执行了a-b,没有执行c,此时的singleton对象只有地址,没有内容。然后cpu又切换到了B线程,
        * 这时singleton == null为false(==比较的是内存地址),
        * 则代码会直接执行到了return,返回一个未初始化的对象(只有地址,没有内容)。
        * */
        private volatile static Singleton singleton;
    
        private Singleton() {
        }
    
        public static Singleton getSingleton() {
            /*第一层检查,检查是否有引用指向对象,高并发情况下会有多个线程同时进入
            * ①当多个线程第一次进入,所有线程都进入if语句
            * ②当多个线程第二次进入,因为singleton已经不为null,因此所有线程都不会进入if语句,
            * 即不会执行锁,从而也就不会因为锁而阻塞,避免锁竞争*/
            if (singleton == null) {
                /*第一层锁,保证只有一个线程进入,
                * ①多个线程第一次进入的时候,只有一个线程会进入,其他线程处于阻塞状态
                * 当进入的线程创建完对象出去之后,其他线程又会进入创建对象,所以有了第二次if检查
                * ②多个线程第二次是进入不到这里的,因为已被第一次if检查拦截*/
                synchronized (Singleton.class) {
                    /*第二层检查,防止除了进入的第一个线程的其他线程重复创建对象*/
                    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
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    说明: 代码注释已详细讲解volatile在该单例模式的作用,已经双重锁的作用。

    • 优点: 解决了线程阻塞的问题
    • 缺点: 多个线程第一次进入的时候会造成大量的线程阻塞,代码不够优雅。

    5. 静态内部类的方式

    public class Singleton {
    
        private Singleton(){}
    
        private static class LayzInner{
            private static Singleton singleton = new Singleton();
        }
        
        public static Singleton getSingleton(){
            return LayzInner.singleton;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    说明:

    • 优点: 第一次类创建的时候加载,避免了内存浪费,不存在阻塞问题,线程安全,唯一性
    • 缺点: 序列化-漏洞:反射,会破坏内部类单例模式

    6. 枚举单例模式

    public enum EnumSingleton {
        INSTANCE;
        private Singleton singleton;
        EnumSingleton(){
            singleton = new Singleton();
        }
        public Singleton getSingleton(){
            return singleton;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    说明: 单元素的枚举类型已经成为实现Singleton的最佳方法,无法反射创建对象,是特殊的饿汉式。

    7. 静态内部类升级版

    借鉴枚举单例的内部实现的方式

    public class Singleton {
        private Singleton(){
            if(LayzInner.singleton != null){
                throw new RuntimeException("不能够进行反射!");
            }
        }
    
        private static class LayzInner{
            private static Singleton singleton = new Singleton();
        }
    
        public static Singleton getSingleton (){
            return LayzInner.singleton;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    说明:

    • 优点:第一次类创建的时候加载,避免了内存浪费,不存在阻塞问题,线程安全,唯一性,解决了反射会破坏内部类单例模式的问题
    • 缺点:不是官方的

    8. 容器式单例

    public class Singleton {
        private Singleton() {
        }
    
        private static Map<String, Object> ioc = new ConcurrentHashMap<>();
    
        public static Object getBean(String className) {
            synchronized (ioc) {
                if (ioc.containsKey(className)) {
                    Object o = null;
                    try {
                        o = Class.forName(className).newInstance();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    return o;
                } else {
                    return ioc.get(className);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    说明: Spring ioc 单例 是懒汉式 枚举上的升级

    9. ThreadLocal单例

    package com.rf.designPatterns.singleton.threadLocalSingleton;
    
    /**
     * @description: ThreadLocal线程单例,为每一个线程提供一个对象,在访问的时候相互不影响
     */
    public class ThreadLocalSingleton {
    
        //创建ThreadLocal实例对象,并且重写initialValue方法
        private static final ThreadLocal<ThreadLocalSingleton>  threadLocalSingleton =
                new ThreadLocal<ThreadLocalSingleton>(){
            @Override
            protected ThreadLocalSingleton initialValue() {
                return new ThreadLocalSingleton();
            }
        };
    
        //构造方法
        private ThreadLocalSingleton(){
    
        }
    
        //提供对外暴露的方法,获取ThreadLocalSingleton对象
        public static ThreadLocalSingleton getInstance(){
            return threadLocalSingleton.get();
        }
    }
    
    • 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

    说明:

    局部单例模式:某一个线程里唯一

    ①ThreadLocal的作用呢,是提供线程内的局部变量,在多线程环境访问时,能保证各个线程内的ThreadLocal变量各自独立。也就是说每个线程的ThreadLocal变量是自己专用的,其他线程是访问不到的。

    ②ThreadLocal最常用于在多线程环境下存在对非线程安全对象的并发访问,而且该对象不需要在线程内共享,如果对该对象加锁,会造成大量线程阻塞影响程序性能,这时候就可以使用ThreadLocal来使每个线程都持有该对象的副本,这是典型的空间换取时间从而提高执行效率的方式。例如项目里经常使用的SimpleDateFormat日期格式化对象,该对象是线程不安全的,而且不需要在线程内共享,因此可以使用ThreadLocal保证其线程安全。

    回到顶部


  • 相关阅读:
    C语言葵花宝典之——文件操作
    Java面试题大全(整理版)1000+面试题附答案详解,最全面详细,看完稳了
    Android简易音乐重构MVVM Java版-BottomNavigationView+viewpager主界面结构(十一)
    python pyqt5 计算下载文件的进度百分比
    全功能知识付费源码系统微信小程序+H5+PC端一站通行,自定义你的小程序
    11 月 18 日 ROS 学习笔记——可视化和调试工具
    vscode自动添加文件和函数注释
    springboot+layui 整合百度富文本编辑器ueditor入门使用教程(踩过的坑)
    机器学习的分类
    【译】Visual Studio Enterprise 中的代码覆盖率特性
  • 原文地址:https://blog.csdn.net/weixin_47410172/article/details/126584060