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

在这里插入图片描述

文章目录

  • 前言
  • 一、基本介绍
    • 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.hqwc.cn/news/641613.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[RTOS 学习记录] 工程管理工具make及makefile

[RTOS 学习记录] 工程管理工具make及makefile 这篇文章是我阅读《嵌入式实时操作系统μCOS-II原理及应用》后的读书笔记,记录目的是为了个人后续回顾复习使用。 前置内容: 开发工具 Borland C/C 3.1 精简版 文章目录 1 make 工具2 makefile 的内容结构3…

去雾笔记01-SRKTDN: Applying Super Resolution Method to Dehazing Task

文章目录 Abstract1. Introduction2. Related Work3. Method3.1. Network Architecture Abstract 们提出了一种结合超分辨方法和知识转移方法的模型。我们的模型由一个教师网络、一个去雾网络和一个超分辨率网络组成。 1. Introduction ECNU KT团队提出了一个知识蒸馏[20]模…

发布自己的Docker镜像到DockerHub

学会了Dockerfile生成Docker image 之后,如何上传自己的镜像到 DockerHub呢?下面我以自己制作的 bs-cqhttp 镜像为例,演示一下如何将自己的镜像发布到 Docker 仓库。 1 生成自己的 Docker 镜像 1.1 实例镜像用到的文件 图1 实例镜像制作用到…

ADOP带您了解高性能GPU服务器基础知识(下篇)

众所周知,在大型模型训练中,通常采用每台服务器配备多个GPU的集群架构。在上一篇文章《 》中,我们对GPU网络中的核心术语与概念进行了详尽介绍。本文将进一步深入探讨常见的GPU系统架构。 📈8台配备NVIDIA A100 GPU的节点/8台…

硬件知识学习 电感

1.电感的作用: 电感在电路中扮演着多样化的关键角色,其作用相当复杂且精细。以下是对电感作用的更详细解释: 滤波作用: 高频噪声抑制:电感能有效滤除电路中的高频噪声和杂波,使得输出端的信号更为平稳和稳定…

JavaEE初阶——多线程(七)——定时器

T04BF 👋专栏: 算法|JAVA|MySQL|C语言 🫵 小比特 大梦想 此篇文章与大家分享多线程的第七篇文章——关于定时器 如果有不足的或者错误的请您指出! 目录 4.定时器4.1标准库提供的定时器4.2自己实现一个定时器4.2.1任务类4.2.2Timer类4.2.3 有一个线程来负…

Oracle数据库的简单使用

Oracle简单使用 一、数据库的介绍二、Oracle介绍账号管理Oracle的安装Oracle服务的作用OracleRemExecService服务创建数据库 常用命令 三、SQL语言SQL分类实用的数据表添加注释数据操纵语言(DML)查询语句(SELECT)wherelikedistinc…

页面分页打印,echarts图解决办法;生成PDF

1:echarts图片前端打印不是很完美,对于VUE2.0版本不是很有好 2:360浏览器不支持vue的最新版本的插件vue3-print-nb 3:vue-print-nb 可以打印带有echarts 一页内容,并且还存在bug,第一次点击打印没有&…

c#学习入门1

一、环境配置 颜色主题 字体设置 行号设置 二、第一个应用程序 1. 在解决方案下创建一个新项目 第一种注释:两杠注释 第二种注释:星号注释 第三种注释:三杠注释(只有在花括号后面输出才会自动补全) 2.控制台输入打印基础语句 输…

vLLM-prefix浅析(System Prompt,大模型推理加速)

原文:vLLM-prefix浅析(System Prompt,大模型推理加速) 简介 本文浅析了在大模型推理加速方面一个非常优秀的项目 vLLM 的一个新特性 Prefix。在 Prompt 中有相同前缀时可以提高吞吐量降低延迟,换句话说可以省去这部分…

Docker镜像下载

离线安装:物理传输 # 将镜像压缩成tar包 (将nginx 镜像打成tar包,然后拷贝到新机器) docker save -o xxxx.tar nginx:v1.0 # 新的机器加载 docker load -i xxx.tar # 在线安装:先上传到远程镜像仓库 docker tag nginx:v1.0 ldj/nginx:v1.0 #登…

【Java框架】Spring框架(四)——Spring中的Bean的创建与生命周期

目录 SpringBean的创建步骤后置处理器(PostProcessor)BeanFactoryPostProcessorBeanPostProcessorInstantiationAwareBeanPostProcessorpostProcessBeforeInstantiationpostProcessAfterInstantiationpostProcessProperties SmartInstantiationAwareBeanPostProcessordetermine…