01、创建型-单例模式--只有一个实例

news2024/10/7 10:23:02

在这里插入图片描述

文章目录

  • 前言
  • 一、基本介绍
    • 1.1 什么是单例模式
    • 1.2 为什么要用单例模式
    • 1.3 应用场景
    • 1.4 单例优缺点
  • 二、单例模式的实现方式
    • 2.1 饿汉式单例
      • 2.1.1 静态变量方式
      • 2.1.2 静态代码块
    • 2.2 懒汉式单例
      • 2.2.1 懒汉式单例
      • 2.2.2 懒汉式优化①-线程安全
      • 2.2.2 懒汉式优化②-双重检查锁
      • 2.2.3 懒汉式优化③-静态内部类
  • 三、小结

前言

  单例模式是设计模式中最简单但又最常用的的设计模式之一,是很多人学的第一个设计模式。引用百度百科的定义:单例模式创建的类在当前进程中,保证一个类只会被实例化一次,并提供了全局访问点,使用的时候通过单例提供的方法来获取实例。在确保线程安全的前提下,很多时候我们只需要同一个类的一个实例即可,而不是在任何使用的地方都实例化一个新对象,因此只能生成一个实例的模式就是单例模式。

一、基本介绍

1.1 什么是单例模式

  单例模式(Singleton Pattern) 是 Java 设计模式中最简单的设计模式之一,是指在内存中 只会且仅 创建一次对象的设计模式,无论什么时候都要保证这一点。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
  单例模式是指一个类只有一个实例,且该类能自行创建这个实例的一种模式,使用的时候通过单例提供的一个静态方法来获取实例。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。单例模式在现实生活中的应用也非常广泛,例如公司 CEO、部门经理等都属于单例模型,单例模式就是为了实现全局一个实例的需求,如下图所示。
在这里插入图片描述

注意:

  • 单例类保证内存里只有一个实例,减少了内存的开销。
  • 单例类必须自己创建自己的唯一实例
  • 单例类必须给所有其他对象提供这一实例

1.2 为什么要用单例模式

  在程序中多次使用同一个对象且作用相同时,对象需要频繁的创建和销毁的时候,而创建和销毁的过程由jvm执行,我们无法对其进行优化,为了防止内存飙升、减少了内存开支,单例模式可以让程序仅在内存中创建一个对象,并提供一个访问它的全局访问点,让所有需要调用的地方都共享这一单例对象。单例模式包含的角色只有一个,就是单例类——Singleton。

Heap
singleton
class1
class2
class3
.....
method1
method2
method3

  单例模式可以避免对资源的多重占用,避免出现多线程的复杂问题。单例类拥有一个私有构造函数,确保用户无法通过new关键字直接实例化它。除此之外,该模式中包含一个静态私有成员变量与静态公有的工厂方法,该工厂方法负责检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。

1.3 应用场景

  • 在应用场景中,某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
  • 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
  • 当某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
  • 应用程序的日志应用,一般都是单例模式实现,只有一个实例去操作才好,否则内容不好追加显示。
  • 多线程的线程池的设计一般也是采用单例模式,因为线程池要方便对池中的线程进行控制。

1.4 单例优缺点

  • 优点:
    1. 在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就防止其它对象对自己的实例化,确保所有的对象都访问一个实例。
    2. 单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
    3. 由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能,避免对共享资源的多重占用。
  • 缺点:
    1. 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
    2. 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
    3. 单例类的职责过重,在一定程度上违背了"单一职责原则"。
    4. 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

二、单例模式的实现方式

  通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。单例模式分为如下两类:

分类说明
饿汉式类加载的时候就会创建实例对象。
懒汉式类加载不会创建实例对象,而是首次使用该对象时才会创建。

2.1 饿汉式单例

2.1.1 静态变量方式

  饿汉式单例模式在类初实话的时候就会进行实例化,简单理解类似于一个"饥饿"的人,在程序启动时就要马上吃饭(创建实例),不管后续是否会真正需要使用这个实例。
  该模式的特点是通过静态修饰符修饰,类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了,在程序调用时直接返回该单例对象即可。这种方式比较常用,调用效率高,在类加载时就创建实例对象,因此不存在 线程安全 的问题。但不管程序用不用,实例都早以创建好,这对内存来说是种浪费,容易产生垃圾对象。

程序启动
类加载
创建单例对象
适用单例对象
返回单例对象
public class HungrySingleton {
    // 创建私有变量 ourInstance,用以记录 Singleton 的唯一实例。
    private static HungrySingleton HungryInstance = new HungrySingleton();
    
    // 把类的构造方法私有化,不让外部调用构造方法实例化
    private HungrySingleton() {}

    // 定义公有方法提供该类的全局唯一访问点,外部通过调用getInstance()方法来返回唯一的实例。
    public static HungrySingleton getInstance() {
        return HungryInstance;
    }
}

  饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。"饿汉式"比起“懒汉式”最大的优点就是没有加锁,执行效率会提高很多,但由于是在类加载时就初始化,容易产生垃圾对象,可能会导致资源浪费,尤其是在实例对象较大或者初始化过程较为复杂的情况下。

2.1.2 静态代码块

这种方式和上面没有基本没有什么区别,都会浪费内存。

public class HungrySingleton {
    private static HungrySingleton instance;

    private HungrySingleton() {
    }

    static {
        instance = new HungrySingleton();
    }

    public static HungrySingleton getInstance() {
        return instance;
    }
}

2.2 懒汉式单例

2.2.1 懒汉式单例

  懒汉式创建对象的方法是在真正使用对象前,先判断该对象是否已经实例化(判空),若已实例化直接返回该类对象,否则先执行实例化操作。只有在需要时才创建实例对象,节省了系统资源,具备懒加载功能。如下图所示:

YES
NO
适用单例对象
是否实例化
返回单例对象
实例化对象

  该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。我们创建一个 LazySingleton 类,LazySingleton 类将其构造函数作为私有,并具有自身的静态实例,使用 LazySingleton 类提供静态方法来获取 LazySingleton 对象。

public class LazySingleton {
    // 使用静态变量保存唯一实例
    private static LazySingleton instance;

    // 将构造方法设为私有,防止外部实例化
    private Singleton() {
    }

    // 提供全局访问点,获取唯一实例
    public static LazySingleton getInstance() {
        return instance == null ? new LazySingleton() : instance;
    }
}

  这种方式是最基本的实现方式,这种实现最大的好处就是第一次调用才初始化,如果没有用到该类,那么就不会实例化,从而节约资源,避免内存浪费。但缺点也比较明显,在多线程环境下是不安全的,如果多个线程能够同时进入,并且此时 instance 为 null,那么会有多个线程执行 instance == null,这将导致多次实例化 instance。

2.2.2 懒汉式优化①-线程安全

  不就是多线程嘛,加锁!由此,你得出了第一种优化方案,给 getInstance() 方法加锁:

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {
        System.out.println("构造参数初始化");
    }
    
    // 保证每次只有一个线程进入getInstance()方法
    public static synchronized LazySingleton getInstance() {
        return instance == null ? new LazySingleton() : instance;
    }
}

  因为考虑到了多线程机制,实现起来比较麻烦,并且还会出现问题,就算是使用了一定的解救办法(同步、加锁、双重判断)的办法,保证在一个时间点只能有一个线程能够进入该方法,从而避免多次实例化 instance 的问题。但是当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,性能还是被损耗了,因此懒汉式方法的不推荐使用。

2.2.2 懒汉式优化②-双重检查锁

  很显然,加锁之后,达到了我们的目的,既实现了懒加载特效,也解决了线程安全问题。但是加锁之后,会导致该方法的执行效率特别低,其实就是初始化的时候才会出现线程安全,一旦初始化完成就不存在了。因此,需要把锁的粒度降低。不在方法上加锁,在关键问题上上锁。

public class Singleton {
    private Singleton() {
    }

    /**
     * 和饿汉模式相比,这边不需要先实例化出来,注意这里的 volatile,它是必须的
     */
    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            // 加锁
            synchronized (Singleton.class) {
                // 这一次判断也是必须的,不然会有并发问题
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

  通过双重检验锁机制来确保只有一个实例对象被创建,解决了懒汉式单例模式的性能、线程安全等问题,看起来是完美无缺的,其实是存在问题的,在多线程下,可能会出现空指针问题。

2.2.3 懒汉式优化③-静态内部类

  静态内部类单例模式由内部类创建,结合了懒汉式和饿汉式各自的优点,利用类加载机制保证了静态内部类只会被加载一次,并初始化其静态属性,从而保证了单例的线程安全性。而只有在需要时才会加载静态内部类,从而实现了延迟加载。

public class Singleton {
    // 私有的静态内部类
    private static class SingletonHolder {
        // 私有的静态变量
        private static final Singleton singletonFour = new Singleton();
    }
    
    private Singleton (){
        System.out.println("构造参数初始化");
    }

    public static final Singleton getInstance() {
        return SingletonHolder.singletonFour;
    }
}

  代码中增加了内部静态类 SingletonHolder,内部有一个singletonFour 的实例,并且也是类级别的。当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getInstance() 方法从而触发 SingletonHolder.singletonFour 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例。这种方式不仅具有延迟初始化的好处,而且由虚拟机提供了对线程安全的支持。看起来像是饿汉式,其实这是懒汉式。因为内部静态类是现在第一次使用的时候才会去初始化,所以SingletonHolder最初并未被初始化。

三、小结

  综上所述,单例模式作为一种简单但又非常重要的设计模式,在实际开发中有着广泛的应用,特别是在Spring框架等大型应用程序中。单例模式虽然简单,但是想写的严谨,还是需要考虑周全。
  当一个类的对象只需要或者只可能有一个时,应该考虑单例模式。如果一个类的实例应该在 JVM 初始化时被创建出来,应该考虑使用饿汉式。如果一个类的实例不需要预先被创建,也许这个类的实例并不一定能用得上,也许这个类的实例创建过程比较耗费时间,也许就是真的没必要提前创建,那么应该考虑懒汉式。非线程安全的懒汉式只能用于非并发的场景,局限性比较大,并不推荐使用。

把今天最好的表现当作明天最新的起点…….~

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1625980.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣-1832.判断句子是否全为字母句

思路: 首先,我们初始化了一个长度为 26 的布尔值列表 exist,所有值都为 False,表示所有字母初始都未出现过。然后,我们遍历输入的字符串 sentence 中的每个字符。对于每个字符,我们通过计算其 ASCII 码值减去字母 a 的…

ArcGIS Pro专题地图系列教程

专题地图系列是ArcGIS Pro3.2的新功能。之前,如果要做8张相同区域的专题图,可能需要新建8个布局,分别进行排版,再导出。现在,一幅地图,一个布局,就可以完成这个流程。 原理是,根据单…

ROS python实现乌龟跟随

产生两只乌龟,中间的乌龟(A) 和 左下乌龟(B), B 会自动运行至A的位置,并且键盘控制时,只是控制 A 的运动,但是 B 可以跟随 A 运行 乌龟跟随实现的核心,是乌龟A和B都要发布相对世界坐标系的坐标信息,然后&am…

用例整体执行及pytest.ini文件

在我们写代码的过程中,一般都是右键或者命令行去执行一个用例 但是当我们写完后,需要整体执行一遍。那应该怎么搞呢? 我们可以在根目录下新建一个main.py或者run.py之类的文件,文件内容如下: if __name__ "__ma…

报错:图片验证码接口对接vue+springboot(下一个笔记会记录整个验证码的代码)

问题:空指针异常ai: 根据错误堆栈信息中提供的方法调用位置,看起来空指针异常是在 AuthCodeServiceImpl 类的 authUserCoded 方法的第 41 行发生的。 为了解决这个问题,你可以检查 AuthCodeServiceImpl 类中 authUserCoded 方法的第 41 行&am…

发那科FANUC机器人R-2000iB平衡缸维修攻略

在发那科机器人中,平衡缸扮演着稳定机械臂运动的关键角色。它通过内部的压力调节来平衡负载,保证机器人的精准定位和平稳操作。一旦出现法兰克机械手平衡缸故障或损坏,机器人的性能可能会大打折扣,因此及时且正确的FANUC机械手平衡…

2024通信会|迈向智慧配电网建设新时代,锐捷网络发布双平面配电通信解决方案

近期,以“加快推进通信数智化,助力构建新型能源体系”为主题的2024年能源网络通信创新应用大会在四川成都圆满结束,会议围绕构建新型能源体系和新型电力系统建设需求,探讨能源网络通信创新应用的最新趋势与成果、“主、配、微”通信网一体化融合、配电通信网、通信数智化转型等…

Methoxy-PEG-PCL,Methoxy-PEG-Poly(ε-caprolactone)可以作为制备纳米颗粒的重要原料

【试剂详情】 英文名称 mPEG-PCL,MPEG-Poly(ε-caprolactone),Methoxy-PEG-PCL,Methoxy-PEG-Poly(ε-caprolactone) 中文名称 聚乙二醇单甲醚聚己内酯两嵌段共聚物, 聚乙二醇单甲醚聚己内酯 外观性状 由分子量决定&#xff0…

C++实战演练---负载均衡在线oj项目预热

顾得泉:个人主页 个人专栏:《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂,年薪百万! 前言 学习准备了快一年时间,心心念念的实战演练终于可以开始了,话不多说,直接进入主题…

如何利用交易形态的失败进行现货黄金?

进行现货黄金理财,除了需要投资者对黄金投资有热情之外,有方法也是很重要的,光有热情而没有技术,我们的资金很可能会成为其他人的囊中之物。但如果有了现货黄金理财的技术,情况就可能扭转过来。下面我们就从买入的角度…

实战对抗DDoS攻击

引言 在当今数字化时代,网络安全威胁无处不在,其中分布式拒绝服务(DDoS)攻击因其强大的破坏性和难以防范性而备受关注。DDoS攻击通过控制多个僵尸节点向目标服务器发送海量请求,耗尽其网络带宽或计算资源,…

C语言面试题之相交链表

相交链表 实例要求 1、给定两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。2、如果两个链表不存在相交节点,返回 null 。示例: 实例分析 可以使用两种方法:哈希表方法和双指针方法。哈希表方法…

平抑风电波动的电-氢混合储能容量优化配置

这篇论文中的EMD分解法在非线性扰动信号分解上优于小波分解法,EMD分解出来的imf各频次信号,继而利用C2F实现信号重构,根据最大波动量限值剔除出需要储能平抑的波动量,继而用超级电容实现平抑(论文中用的碱水电解槽+燃料电池我认为有很多个点可以佐证不合适,但是电制氢是热…

惠州工厂降温、惠州惠城惠阳大亚湾仲恺高新惠城博罗车间降温通风

惠州工厂降温、惠州惠城惠阳大亚湾仲恺高新惠城博罗车间降温通风,惠州博罗车间降温、 惠州博罗的车间降温是一个涉及提高工作环境舒适度与生产效率的重要问题。以下是一些建议的降温方法: 通风换气:对于车间来说,良好的通风是降…

什么是独立服务器?独立服务器最全面介绍

独立服务器是单个客户端具有独占访问权的服务器类型,在服务器开发过程中,以前使用虚拟主机和虚拟独立服务器(VPS)的公司几乎不可避免地转向独立服务器。 什么是独立服务器? 独立服务器是单个客户端具有独占访问权的服…

4.28|重量级嘉宾携卓翼飞思RflySim平台亮相国际盛会,内容抢先看!

一. 大会背景 2024国际无人机应用及防控大会暨无人机产业博览会即将拉开帷幕,一场高规格、高水平的无人机产业应用国际盛会将再次点亮科技界的星空。 该大会由中国无人机产业创新联盟联合各方有影响力的单位,于4月27-29日在北京举办。组委会致力于将会…

亿道三防onerugged|三防车载电脑在港口货柜车上的应用

作为一个专业人员,我深知在港口货柜车运输中,三防车载电脑的应用对于提高工作效率和解决实际问题的重要性。亿道三防onerugged系列产品的三防车载电脑以其卓越的功能特点和可靠性,为港口货柜车运输带来了深远的影响。 首先,三防车…

Vue3 + Element-Plus 对接高德地图实现搜索提示选址、点击地图选址、自我定位功能(最新)

Vue3 Element-Plus 对接高德地图实现搜索提示选址、点击地图选址、自我定位功能&#xff08;最新&#xff09; 1、效果展示2、实现代码2.1 GaoDeMap.vue2.2 SystemDialog.vue2.3 UnusedList.vue.vue 1、效果展示 2、实现代码 2.1 GaoDeMap.vue <template><div style…

关于C++如何导出dll用于C++和C#的研究(内含有YOLOV5调用接口的制作完整代码)

文章目录 一、VS2019复现视频的易错点。二、[补充一个C接口的知识](https://www.bilibili.com/video/BV1tA411N7HB/?spm_id_from333.337.search-card.all.click&vd_sourcef99e21db912f182fe051b1b6b156e0e3)三、回归正题如何调用dll&#xff1f;前面第一个视频教会我们导出…

07 流量回放实现自动化回归测试

在本模块的前四讲里&#xff0c;我向你介绍了可以直接落地的、能够支撑百万并发的读服务的系统架构&#xff0c;包含懒加载缓存、全量缓存&#xff0c;以及数据同步等方案的技术细节。 基于上述方案及细节&#xff0c;你可以直接对你所负责的读服务进行架构升级&#xff0c;将…