单例设计模式(Singleton Patten)

本文详细介绍了Java中的单例设计模式,包括懒汉式、饿汉式和双重检查式创建方法,以及各自的优缺点。在懒汉式中,对象在使用时才创建,节省内存但首次访问效率较低;饿汉式则在类加载时即创建单例,占用内存但访问效率高。双重检查式解决了懒汉式的线程安全问题,实现了延迟初始化和线程安全。单例模式的主要优点是内存中唯一实例,减少资源消耗,方便统一管理,但也存在扩展困难和测试不便的问题。
摘要由CSDN通过智能技术生成

单例设计模式

1.1 单例设计模式概述

1.1.1 Gof设计模式简介

  • Gof:1995 《设计模式》 Gang of Fore
  • 23种基于面向对象的设计模式

1.1.2 为什么需要单例模式

实际项目开发中,经常遇到一些对象,这样的对象在全局中仅存在有一个就可以了,如果对象出现多个的话,就会造成程序整体执行流程失败,或者内存管理上的问题。

1.1.3 单例模式概述

单例模式是确保某一个类只有一个实例,而且这个实例一般都是自行实例化,通过一个全局访问点,向整个系统提供这个实例

1.2 单例模式创建

1.2.1 懒汉式创建

1.2.1.1 创建方法

对象在成员变量上,只声明,不创建。
当我们在使用到该变量的时候,才会创建出来该对象

1.2.1.2 案例代码:

1.创建项目结构
在这里插入图片描述
2. 代码

  • 2.1 最开始的代码

    FootBall.java类:

    package com.renbiao.pojo;
    public class FootBall {
    }
    

    Test.java测试类

    package com.renbiao.test;
    import com.renbiao.pojo.FootBall;
    
    public class Test {
        public static void main(String[] args) {
            FootBall fb1 = new FootBall();
            FootBall fb2 = new FootBall();
            FootBall fb3 = new FootBall();
    
            System.out.println(fb1);
            System.out.println(fb2);
            System.out.println(fb3);
        }
    }
    

    运行结果:

    com.renbiao.pojo.FootBall@71dac704
    com.renbiao.pojo.FootBall@123772c4
    com.renbiao.pojo.FootBall@2d363fb3

    分析:

    可以看出输出的三个地址都不相同,即是三个对象。
    三个对象分别来自三块堆,一块堆代表一个对象。

  • 2.2 第一次优化代码

    不能让程序随便的去new对象,需要将构造方法私有化。
    这样其他程序就不能随便new对象了,想要获取该对象,就要由该类提供方法

    FootBall.java类:

    package com.renbiao.pojo;
    
    public class FootBall {
    
        // 构造方法私有化
        private FootBall(){
        }
    
        // 想要得到该对象,必须由该类提供方法
        // 但是该方法不是一个static静态方法,外部暂时不能调用
        public FootBall getFootBall(){
            FootBall fb = new FootBall();
            return fb;
        }
    }
    

    Test.java测试类

    可以看到下面:FootBall中没有提供可利用的方法

    在这里插入图片描述

  • 2.3 静态方法创建和调用

    getFootBall()方法必须是一个static静态方法。只有静态方法,才能够使用类去调用

    FootBall.java类:

    package com.renbiao.pojo;
    
    public class FootBall {
    
        // 构造方法私有化
        private FootBall(){
        }
    
        // 想要得到该对象,必须由该类提供方法
        // 该方法必须是一个static静态方法。只有静态方法,才能够使用类去调用
        public static FootBall getFootBall(){
            FootBall fb = new FootBall();
            return fb;
        }
    }
    

    Test.java测试类

    package com.renbiao.test;
    import com.renbiao.pojo.FootBall;
    
    public class Test {
    
        public static void main(String[] args) {
    
            FootBall fb1 = FootBall.getFootBall();
            FootBall fb2 = FootBall.getFootBall();
            FootBall fb3 = FootBall.getFootBall();
    
            System.out.println(fb1);
            System.out.println(fb2);
            System.out.println(fb3);
        }
    }
    

    运行结果:

    com.renbiao.pojo.FootBall@71dac704
    com.renbiao.pojo.FootBall@123772c4
    com.renbiao.pojo.FootBall@2d363fb3

    分析:

    可以看出输出的三个地址都不相同,即是三个对象。
    虽然我们在测试时,没有重新创建对象,
    但是在调用的方法中,每次调用都会new一个对象,
    本质上原理没变,只是和原来new对象的位置发生了改变,结果没变。

  • 2.4 改变为单例模式

    声明一个成员变量,但是不new,用到的时候判断是否要再创建。

    FootBall.java类:

    package com.renbiao.pojo;
    
    public class FootBall {
    
        // 将该类的对象,声明成为成员变量
        // 下方的getFootBall()方法要使用到该成员变量,
        // 如果一个静态方法要直接的使用成员变量,那么成员变量也必须是静态的
        private static FootBall fb;
    
        // 构造方法私有化
        private FootBall(){
        }
    
        // 想要得到该对象,必须由该类提供方法
        // 该方法必须是一个static静态方法。只有静态方法,才能够使用类去调用
        public static FootBall getFootBall(){
            //如果成员变量是null,说明成员变量还没有被实例化过,需要创建给成员变量实例
            //该实例作为单例对象,为全局使用
            //第一次访问时,创建对象,以后再访问时,不再创建,取得的是第一次创建的对象
            if(fb == null){
                fb = new FootBall();
            }
            return fb;
        }
    }
    

    Test.java测试类

    package com.renbiao.test;
    import com.renbiao.pojo.FootBall;
    
    public class Test {
    
        public static void main(String[] args) {
    
            FootBall fb1 = FootBall.getFootBall();
            FootBall fb2 = FootBall.getFootBall();
            FootBall fb3 = FootBall.getFootBall();
    
            System.out.println(fb1);
            System.out.println(fb2);
            System.out.println(fb3);
        }
    }
    

    运行结果:

    com.renbiao.pojo.FootBall@71dac704
    com.renbiao.pojo.FootBall@71dac704
    com.renbiao.pojo.FootBall@71dac704

    分析:

    对象在成员变量上,只声明,不创建。
    当我们在使用到该变量的时候,才会创建出来该对象。
    第一次访问创建出来单例对象,
    当第二次到第N次访问时,就不创建对象了,而是取得第一次访问时,创建的对象

1.2.2 饿汉式创建

1.2.2.1 创建方法

当该类加载完毕后,单例对象就已经创建出来了,不管你第几次访问

1.2.2.2 案例代码

1.创建项目结构
在这里插入图片描述
2. 代码

  • 2.1 饿汉式创建

    BasketBall.java

    package com.renbiao.pojo;
    public class BasketBall {
    
        //声明单例对象
        private static BasketBall bb = new BasketBall();
    
        // 构造方法私有化
        private BasketBall() {
        }
        
        // 通过方法唯一的入口点,来获取单例对象
        public static BasketBall getBasketBall() {
            return bb;
        }
    }
    

    Test.java

    package com.renbiao.test;
    import com.renbiao.pojo.BasketBall;
    public class Test {
    
        public static void main(String[] args) {
    
            BasketBall bb1 = BasketBall.getBasketBall();
            BasketBall bb2 = BasketBall.getBasketBall();
            BasketBall bb3 = BasketBall.getBasketBall();
    
            System.out.println(bb1);
            System.out.println(bb2);
            System.out.println(bb3);
        }
    }
    

    运行结果:

    com.renbiao.pojo.BasketBall@71dac704
    com.renbiao.pojo.BasketBall@71dac704
    com.renbiao.pojo.BasketBall@71dac704

    分析:

    不论是第一次访问,还是第几次访问,执行方法是一模一样的,
    该入口已经帮我们准备好一个单例对象,我们通过该入口直接取得对象就可以了。

1.2.3 双重检查式创建

1.2.3.1 创建方法

懒汉式创建的基础上,使用synchronized同步块,并进行两次判断

1.2.3.2 案例代码

1.创建项目结构
在这里插入图片描述
2. 代码

  • 2.1 创建懒汉式单例模式

    VolleyBall.java

    package com.renbiao.pojo;
    public class VolleyBall {
    
        // 创建单例对象
        private static VolleyBall vb;
    
        // 创建无参构造方法
        public VolleyBall(){}
    
        // 提供方法来获取单例对象
        public static VolleyBall getVolleyBall(){
            if(vb == null){
                vb = new VolleyBall();
            }
            return vb;
        }
    }
    

    Test.java

    package com.renbiao.test;
    import com.renbiao.pojo.VolleyBall;
    public class Test {
    
        public static void main(String[] args) {
    
            VolleyBall vb1 = VolleyBall.getVolleyBall();
            VolleyBall vb2 = VolleyBall.getVolleyBall();
            VolleyBall vb3 = VolleyBall.getVolleyBall();
    
            System.out.println(vb1);
            System.out.println(vb2);
            System.out.println(vb3);
        }
    }
    

    运行结果:

    com.renbiao.pojo.VolleyBall@71dac704
    com.renbiao.pojo.VolleyBall@71dac704
    com.renbiao.pojo.VolleyBall@71dac704

    分析:

    这是一个最初的懒汉式单例模式的创建

  • 2.2 双重检查式创建

    因为两根线程同时访问方法时,同时判断是否为空,同时进入if语句块,这样就会new出两个对象,如下图:

在这里插入图片描述

基于上面发生的问题,有以下解决方案

  • 2.2.1 解决方法1:

    方案:使用同步方法

    方法:我们可以将取得的单例对象的方法进行同步的操作,将该方法改造为同步方法。
    问题:访问该方法,可以让两个线程一起访问,如果将该方法改变成为同步方法,在取得该单例对象的时候,就只能一个线程一个线程的排队取得单例对象,会大大降低效率

    代码:

    VolleyBall.java

    package com.renbiao.pojo;
    public class VolleyBall {
    
        // 创建单例对象
        private static VolleyBall vb;
        // 创建无参构造方法
        public VolleyBall(){}
    
        // 提供方法来获取单例对象
        // 方法1:同步方法
        public synchronized static VolleyBall getVolleyBall(){
            if(vb == null){
                vb = new VolleyBall();
            }
            return vb;
        }
    }
    
  • 2.2.2 解决方法2:

    方案:使用synchronized同步块的方法

    方法:将方法从同步方法改为非同步方法,使用synchronized同步块的形式来操作if语句。
    问题:虽然多个线程同时访问到该方法,但是仍然是在同步代码块的代码上进行排队等候,实质上与同步方法没有区别,降低程序的访问效率

    代码:

    VolleyBall.java

    package com.renbiao.pojo;
    public class VolleyBall {
    
        // 创建单例对象
        private static VolleyBall vb;
        // 创建无参构造方法
        public VolleyBall(){}
    
        // 提供方法来获取单例对象
        public synchronized static VolleyBall getVolleyBall(){
            // 方法2:synchronized同步块
            synchronized (VolleyBall.class){
                if (vb == null){
                    vb = new VolleyBall();
                }
            }
            return vb;
        }
    }
    
  • 2.2.3 解决方法3:

    方案:使用双重检查方法

    进行两次if判断
    第一次的判断没有涉及到同步代码块, 不会造成线程排队来取对象的问题。
    第二次的判断是为了让并发的线程没有机会创建第二个对象的可能。

    代码:

    VolleyBall.java

    package com.renbiao.pojo;
    public class VolleyBall {
    
        // 创建单例对象
        private static VolleyBall vb;
        // 创建无参构造方法
        public VolleyBall(){}
    
        // 提供方法来获取单例对象
        public synchronized static VolleyBall getVolleyBall(){
            //方法3:双重检查
            if (vb == null) {
                synchronized (VolleyBall.class) {
                    if (vb == null) {
                        vb = new VolleyBall();
                    }
                }
            }
            return vb;
        }
    }
    

    Test.java

    package com.renbiao.test;
    import com.renbiao.pojo.VolleyBall;
    
    public class Test {
    
        public static void main(String[] args) {
    
            VolleyBall vb1 = VolleyBall.getVolleyBall();
            VolleyBall vb2 = VolleyBall.getVolleyBall();
            VolleyBall vb3 = VolleyBall.getVolleyBall();
    
            System.out.println(vb1);
            System.out.println(vb2);
            System.out.println(vb3);
        }
    }
    

    运行结果:

    com.renbiao.pojo.VolleyBall@71dac704
    com.renbiao.pojo.VolleyBall@71dac704
    com.renbiao.pojo.VolleyBall@71dac704

    分析:

    双重检查的单例模式的创建,可以实现懒汉式模式创建,也可以避免多线程的影响。

1.3 单例模式创建方式选择

  • 懒汉式:

    创建时机:不用时不创建,用到时才创建。

    优点:节约内存空间

    缺点:第一次访问时创建单例对象,第二次到第N次就不用创建新对象了,而是使用第一次创建出来的单例对象。第一次用到(创建单例)对象的执行过程,要比第二次到第N次的执行效率要低,访问效率要慢,因为有一个创建对象的过程。

  • 饿汉式:

    创建时机:不论暂时用不用,都先将对象创建出来

    优点:不论用不用,都有这个单例对象。不论是第一次访问还是第二次第N次访问,对于这个单例对象的取得,都一样,就是将已经创建好的单例对象取出来,直接用就可以了。

    缺点:如果这个单例对象暂时没有用处,就比较占用内存。

  • 双重检查式:

    懒汉式的多线程并发问题的处理,既然是懒汉式的多线程并发问题的扩充,如果我们要使用懒汉式,一般我们都要使用这种扩充后的双重检查形式。

    == 在实际项目中,以上懒汉式和饿汉式都会用到。==

1.4 单例模式的优缺点

1.4.1 优点

  • 由于单例模式在内存中有且只有一个实例,减少了内存的开支。
  • 由于单例模式只生成一个实例,所以减少了系统性能的开销。
  • 单例模式是在系统中,有且只有一个访问点,方便管理。

1.4.2 缺点

  • 单例模式一般情况下都是没有接口的,如果没有接口,对于程序的扩展就会变得困难,如果要扩展,除了修改单例所在类的源代码,没有其他办法。
  • 单例模式对测试是不方便的。在并行环境中,单例模式没有完成,就不能进行有效的测试。

1.5 附录

转载自蛙课网
B站地址:单例设计模式

Gitee代码:
gitee地址:https://gitee.com/gitofrr/wkcto-singleton.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱写代码的小R

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值