• 【设计模式】单例模式


    单例模式属于创建型模式,是最简单的一种设计模式。当一个类在程序中只需要创建唯一全局对象时(如网站计数类、日志管理类、线程池类……),就可以使用单例模式。单例模式规定一个类只能创建一个实例,之后不能再创建新的实例。所有线程无论何时获取的都是该类的唯一对象,这样做节省了创建一个新对象的资源开销。

    单例模式的使用

    ​ 单例模式保证一个类只存在一个对象,并提供访问该对象的静态方法,为了防止该类被多次创建,需要将它的构造方法设置为私有的。

    单例模式类图

    image-20221118191559235

    实现方法

    第一步,创建网站计数器类(单例类)

    网站计数器
    package 设计模式.单例模式;
    
    public class 网站计数器 {
    
        // 静态属性,这里使用的饿汉式实现单例
        private static 网站计数器 唯一实例 = new 网站计数器();
    
        private int 网站点击数;
    
        // 将该类的构造方法设置为私有,导致无法在外部创建对象
        private 网站计数器(){ }
    
        // 获取唯一实例的静态方法
        public static 网站计数器 获取唯一实例(){
            return 唯一实例;
        }
    
        public void 点击数增加(){
            this.网站点击数 += 1;
        }
    
        public int 获取点击数(){
            return this.网站点击数;
        }
    
    }
    
    • 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

    第二步,创建测试类测试

    测试类
    package 设计模式.单例模式;
    
    public class 测试类 {
        public static void main(String[] args) {
            网站计数器 计数器 = 网站计数器.获取唯一实例();
            计数器.点击数增加();
            System.out.println("当前网站点击量为:"+计数器.获取点击数());
            网站计数器 计数器二 = 网站计数器.获取唯一实例();
            计数器二.点击数增加();
            计数器二.点击数增加();
            计数器二.点击数增加();
            System.out.println("当前网站点击量为:"+计数器.获取点击数());
            网站计数器 计数器三 = 网站计数器.获取唯一实例();
            计数器三.点击数增加();
            计数器三.点击数增加();
            System.out.println("当前网站点击量为:"+计数器.获取点击数());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    实现效果

    image-20221118191959145

    单例模式的几种实现方法

    ​ 单例模式的实现方法从线程安全以及资源消耗方面来讲,有线程安全懒汉式线程不安全懒汉式饿汉式双检锁静态内部类登记式枚举式六种

    线程安全的懒汉加载方法

    ​ 此方法以懒加载的形式创建实例,当第一次需要使用该对象时才会创建实例,以此来避免浪费内存。同时,此方法使用synchronzed 同步锁来实现线程安全,保证多个线程同时调用时只会创建单个实例,但加锁难免损耗一些性能。

    演示的单例类
    package 设计模式.单例模式;
    
    public class 网站计数器 {
    
        /* 线程安全懒汉式创建单例 */
        
        // 用静态属性存储单例对象
        private static 网站计数器 唯一实例 ;
        private int 网站点击数;
    
        // 将该类的构造方法设置为私有,导致无法在外部创建对象
        private 网站计数器(){ }
    
        // 获取唯一实例的静态方法
        public static synchronized 网站计数器 获取唯一实例(){
            // 不存在唯一实例时先创建实例
            if (唯一实例 == null){
                唯一实例 = new 网站计数器();
            }
            return 唯一实例;
        }
    
        public void 点击数增加(){
            this.网站点击数 += 1;
        }
    
        public int 获取点击数(){
            return this.网站点击数;
        }
        
    }
    
    • 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

    线程不安全的懒汉加载方法

    ​ 此方法与上面线程安全的懒汉加载方法基本一样,但是此方法没有添加 sycnhronized 同步锁,所以在多线程同时请求时,可能会导致创建多个对象来覆盖之间的实例对象。

    演示的单例类
    public class 网站计数器 {
    
        /* 线程不安全的懒汉式创建单例 */
        
        // 用静态属性存储单例对象
        private static 网站计数器 唯一实例 ;
        private int 网站点击数;
    
        // 将该类的构造方法设置为私有,导致无法在外部创建对象
        private 网站计数器(){ }
    
        // 获取唯一实例的静态方法
        public static 网站计数器 获取唯一实例(){
            // 不存在唯一实例时先创建实例
            if (唯一实例 == null){
                唯一实例 = new 网站计数器();
            }
            return 唯一实例;
        }
    
        public void 点击数增加(){
            this.网站点击数 += 1;
        }
    
        public int 获取点击数(){
            return this.网站点击数;
        }
    
    }
    
    • 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

    饿汉式加载方法

    ​ 这种方法最为简单,同时也是线程安全的。但由于在类加载时就进行了初始化操作占据一部分内存空间,如果要过很久才会调用这个类的话,那么此类就是占着茅坑不拉屎浪费了程序的内存空间

    演示的单例类
    package 设计模式.单例模式;
    
    public class 网站计数器 {
    	/* 饿汉式创建单例 */
        // 静态属性,这里使用的饿汉式实现单例
        private static 网站计数器 唯一实例 = new 网站计数器();
        private int 网站点击数;
    
        // 将该类的构造方法设置为私有,导致无法在外部创建对象
        private 网站计数器(){ }
    
        // 获取唯一实例的静态方法
        public static 网站计数器 获取唯一实例(){
            return 唯一实例;
        }
    
        public void 点击数增加(){
            this.网站点击数 += 1;
        }
    
        public int 获取点击数(){
            return this.网站点击数;
        }
    
    }
    
    • 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

    双检锁加载方法

    ​ 此方法优化了线程安全的懒汉式加载方法,它取消了获取实例方法上的synchronized同步锁,让多个线程获取实例时不需要排队获取,损耗性能。双检锁使用两重检查,先判断实例对象是否存在,如果已经存在则直接返回实例,而不存在实例时再通过同步锁保证线程安全的创建唯一实例。

    演示的单例
    package 设计模式.单例模式;
    
    public class 网站计数器 {
    	/* 双检锁创建单例 */
        // 用静态属性存储单例对象
        private static 网站计数器 唯一实例;
        private int 网站点击数;
    
        // 将该类的构造方法设置为私有,导致无法在外部创建对象
        private 网站计数器(){ }
    
        // 获取唯一实例的静态方法
        public static 网站计数器 获取唯一实例(){
            // 如果唯一实例已经创建则直接返回实例
            if (唯一实例 == null){
                // 多个线程同时准备创建实例时,使用同步锁
                synchronized(网站计数器.class){
                    // 如果有线程已经创建了实例,那此线程就不再创建了
                    if (唯一实例 == null){
                        唯一实例 = new 网站计数器();
                    }
                }
            }
            return 唯一实例;
        }
    
        public void 点击数增加(){
            this.网站点击数 += 1;
        }
    
        public int 获取点击数(){
            return this.网站点击数;
        }
    
    }
    
    • 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

    静态内部类登记方法

    ​ 该方法利用Classloader的加载机制,当类被加载时,并不会加载它的内部类,只有当显式调用该内部类时,该内部类才会被加载。通过该机制,可以实现一种线程安全的懒汉式方法创建单例对象。

    演示的单例类
    package 设计模式.单例模式;
    
    public class 网站计数器 {
    	 /* 静态内部类创建单例 */
        private int 网站点击数;
        static class 内部类{
            private static final 网站计数器 唯一单例 = new 网站计数器();
        }
    
        public static final 网站计数器 获取唯一实例(){
            // 只有外部显示调用此方法时,内部类才会被加载,唯一单例对象才会被创建
            return 内部类.唯一单例;
        }
    
        public void 点击数增加(){
            this.网站点击数 += 1;
        }
    
        public int 获取点击数(){
            return this.网站点击数;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    枚举型加载方法

    ​ 枚举是在jdk1.5后加入的,所以使用的人不是很多,但它却是最为便捷与简单的一种线程安全的单例创建方法。其本质是利用枚举值的唯一性与自动支持序列化的机制,杜绝在反序列化时创建新的对象。

    演示的单例类
    package 设计模式.单例模式;
    
    public enum 枚举类网站计数器 {
        唯一实例;
        private int 网站点击数;
    
        public void 点击数增加(){
            this.网站点击数 += 1;
        }
    
        public int 获取点击数(){
            return this.网站点击数;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    测试类
    package 设计模式.单例模式;
    
    public class 测试类 {
        public static void main(String[] args) {
            枚举类网站计数器 计数 = 枚举类网站计数器.唯一实例;
            System.out.println("当前网站点击量为:"+计数.获取点击数());
            计数.点击数增加();
            System.out.println("当前网站点击量为:"+计数.获取点击数());
            枚举类网站计数器 计数二 = 枚举类网站计数器.唯一实例;
            计数二.点击数增加();
            计数二.点击数增加();
            计数二.点击数增加();
            System.out.println("当前网站点击量为:"+计数二.获取点击数());
            枚举类网站计数器 计数三 = 枚举类网站计数器.唯一实例;
            计数三.点击数增加();
            计数.点击数增加();
            计数三 = 计数二;
            计数三.点击数增加();
            System.out.println("当前网站点击量为:"+计数.获取点击数());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    测试效果

    image-20221118203805758

  • 相关阅读:
    如何为java项目开启Spring Boot模式呢?
    零样本学习
    Python网络爬虫:使用Requests库精通网络请求
    11--无重复字符的最长子串
    NR paging
    硬核!最全Java面试宝典+Java核心知识集一箭双雕杠秋招
    electron模板【lectron-react-boilerplate】多窗口配置【HtmlWebpackPlugin】多页面配置
    某音网页端 X-Bogus 参数
    字节5年经验之谈 —— Selenium 中并行测试的重要性!
    前端 class 和 id 问题
  • 原文地址:https://blog.csdn.net/weixin_45634261/article/details/127931407