当前位置: 首页 > news >正文

设计模式-01-单例设计模式

       经典的设计模式有23种,但是常用的设计模式一般情况下不会到一半,我们就针对一些常用的设计模式进行一些详细的讲解和分析,方便大家更加容易理解和使用设计模式。

1-为什么要使用单例

         单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。

       从业务概念上,有些数据在系统中只应该保存一份,就比较适合设计为单例类。比如,系统的配置信息类。除此之外,我们还可以使用单例解决资源访问冲突的问题。在实际项目开发过程中,比如我们在单体项目中【这里先考虑单体】中我们需要一个类专门生成id,如果有多个实例,会产生重复的id,这种情况是我们不想看到的也是不能发生的,这样的情况我们必须采用单例模式。

2-如何实现一个单例

      实现单例的核心要点:
      1-构造函数需要是private访问权限的,这样才能避免外部通过new创建实例;
      2-考虑对象创建时的线程安全问题;
      3-考虑是否支持延迟加载;
      4-考虑getInstance()性能是否高(是否加锁)

2.1-饿汉式

      饿汉式的实现方式比较简单。在类加载的时候,instance静态实例就已经创建并初始化好了,所以,instance实例的创建过程是线程安全的。不过,这样的实现方式不支持延迟加载(在真正用到IdGenerator的时候,再创建实例)。具体的代码实现如下所示:

public class IdGenerator {private AtomicLong id = new AtomicLong(0);private static final IdGenerator instance = new IdGenerator();private IdGenerator() {}public static IdGenerator getInstance() {return instance;}public long getId() {return id.incrementAndGet();}
}

观点讨论:

       观点1:有人觉得这种实现方式不好,因为不支持延迟加载,如果实例占用资源多(比如占用内存多)或初始化耗时长(比如需要加载各种配置文件),提前初始化实例是一种浪费资源的行为。最好的方法应该在用到的时候再去初始化。

       观点2:如果初始化耗时长,那我们最好不要等到真正要用它的时候,才去执行这个耗时长的初始化过程,这会影响到系统的性能(比如,在响应客户端接口请求的时候,做这个初始化操作,会导致此请求的响应时间变长,甚至超时)。采用饿汉式实现方式,将耗时的初始化操作,提前到程序启动的时候完成,这样就能避免在程序运行的时候,再去初始化导致的性能问题。如果实例占用资源多,按照fail-fast的设计原则(有问题及早暴露),那我们也希望在程序启动时就将这个实例初始化好。如果资源不够,就会在程序启动的时候触发报错(比如Java中的 PermGen Space OOM),我们可以立即去修复。这样也能避免在程序运行一段时间后,突然因为初始化这个实例占用资源过多,导致系统崩溃,影响系统的可用性。

2.2-懒汉式

懒汉式相对于饿汉式的优势是支持延迟加载。

public class IdGenerator {private AtomicLong id = new AtomicLong(0);private static IdGenerator instance;private IdGenerator() {}public static synchronized IdGenerator getInstance() {if (instance == null) {instance = new IdGenerator();}return instance;}public long getId() {return id.incrementAndGet();}
}

       分析:我们给getInstance()这个方法加了一把大锁(synchronzed),导致这个函数的并发度很低。量化一下的话,并发度是1,也就相当于串行操作了。而这个函数是在单例使用期间,一直会被调用。如果这个单例类偶尔会被用到,那这种实现方式还可以接受。但是,如果频繁地用到,那频繁加锁、释放锁及并发度低等问题,会导致性能瓶颈,这种实现方式就不可取了。

2.3-双重检测

       饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。那我们再来看一种既支持延迟加载、又支持高并发的单例实现方式,也就是双重检测实现方式。在这种实现方式中,只要instance被创建之后,即便再调用getInstance()函数也不会再进入到加锁逻辑中了。所以,这种实现方式解决了懒汉式并发度低的问题。 注意:添加volatile关键字,保证instance = new IdGenerator(); 完全执行完成。

public class IdGenerator {private AtomicLong id = new AtomicLong(0);private static volatile IdGenerator instance;private IdGenerator() {}public static IdGenerator getInstance() {if (instance == null) {synchronized(IdGenerator.class) { // 此处为类级别的锁if (instance == null) {instance = new IdGenerator();}}}return instance;}public long getId() {return id.incrementAndGet();}
}

2.4-静态内部类

       我们再来看一种比双重检测更加简单的实现方法,那就是利用Java的静态内部类。它有点类似饿汉式,但又能做到了延迟加载。

public class IdGenerator {private AtomicLong id = new AtomicLong(0);private IdGenerator() {}private static class SingletonHolder{private static final IdGenerator instance = new IdGenerator();}public static IdGenerator getInstance() {return SingletonHolder.instance;}public long getId() {return id.incrementAndGet();}
}

       SingletonHolder 是一个静态内部类,当外部类IdGenerator被加载的时候,并不会创建SingletonHolder实例对象。只有当调用getInstance()方法时,SingletonHolder才会被加载,这个时候才会创建instance。insance的唯一性、创建过程的线程安全性,都由JVM来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。

2.5-枚举

       基于枚举类型的单例实现。这种实现方式通过Java枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。

public enum IdGenerator {INSTANCE;private AtomicLong id = new AtomicLong(0);public long getId() { return id.incrementAndGet();}
}

3-单例存在的问题

       大部分情况下,我们在项目中使用单例,都是用它来表示一些全局唯一类,比如配置信息类、连接池类、ID生成器类。单例模式书写简洁、使用方便,在代码中,我们不需要创建对象,直接通过类似IdGenerator.getInstance().getId()这样的方法来调用就可以了。但是,这种使用方法有点类似硬编码(hard code),会带来诸多问题。

3.1-单例对OOP特性的支持不友好

       OOP的四大特性是封装、抽象、继承、多态。单例这种设计模式对于其中的抽象、继承、多态都支持得不好。

       比如:订单业务,我们生成订单id 代码 long id = IdGenerator.getInstance().getId();用户业务我们生成用户id  long id = IdGenerator.getInstance().getId();

       IdGenerator的使用方式违背了基于接口而非实现的设计原则,也就违背了广义上理解的OOP的抽象特性。如果未来某一天,我们希望针对不同的业务采用不同的ID生成算法。比如,订单ID和用户ID采用不同的ID生成器来生成。为了应对这个需求变化,我们需要修改所有用到IdGenerator类的地方,这样代码的改动就会比较大。

3.2-单例会隐藏类之间的依赖关系

        单例类不需要显示创建、不需要依赖参数传递,在函数中直接调用就可以了。如果代码比较复杂,这种调用关系就会非常隐蔽。在阅读代码的时候,我们就需要仔细查看每个函数的代码实现,才能知道这个类到底依赖了哪些单例类。

3.3-单例对代码的扩展性不友好

       单例类只能有一个对象实例。如果未来某一天,我们需要在代码中创建两个实例或多个实例,那就要对代码有比较大的改动。

3.4-单例对代码的可测试性不友好

       单例模式的使用会影响到代码的可测试性。如果单例类依赖比较重的外部资源,比如DB,我们在写单元测试的时候,希望能通过mock的方式将它替换掉。而单例类这种硬编码式的使用方式,导致无法实现mock替换。 

3.5-单例不支持有参数的构造函数

      单例不支持有参数的构造函数,比如我们创建一个连接池的单例对象,我们没法通过参数来指定连接池的大小。怎么解决呢?

public class Config {public static final int PARAM_A = 123;public static final int PARAM_B = 245;
}public class Singleton {private static Singleton instance = null;private final int paramA;private final int paramB;private Singleton() {this.paramA = Config.PARAM_A;this.paramB = Config.PARAM_B;}public synchronized static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

4-单例有什么替代解决方案

       如果要完全解决这些问题,我们可能要从根上,寻找其他方式来实现全局唯一类了。比如,通过工厂模式、IOC容器(比如Spring IOC容器)来保证,由过程序员自己来保证(自己在编写代码的时候自己保证不要创建两个类对象)。

5-小结

       有人把单例当作反模式,主张杜绝在项目中使用。我个人觉得这有点极端。模式没有对错,关键看你怎么用。如果单例类并没有后续扩展的需求,并且不依赖外部系统,那设计成单例类就没有太大问题。对于一些全局的类,我们在其他地方new的话,还要在类之间传来传去,不如直接做成单例类,使用起来简洁方便。

相关文章:

设计模式-01-单例设计模式

经典的设计模式有23种,但是常用的设计模式一般情况下不会到一半,我们就针对一些常用的设计模式进行一些详细的讲解和分析,方便大家更加容易理解和使用设计模式。 1-为什么要使用单例 单例设计模式(Singleton Design Pattern&…...

霍尔电流传感器如何进行可靠性测试?主要应用在哪些领域?

霍尔电流传感器广泛应用于航空航天、电源监测、飞行器状态监测、变速驱动设备、焊接设备供电电源、新能源汽车蓄电池管理系统等领域,在电流检测领域中有着重要地位和实用价值,在电驱系统中被称为新能源汽车的动力“心脏”。因此,霍尔电流传感…...

pandas按行按列遍历Dataframe的三种方式

遍历数据有以下三种方法: 简单对上面三种方法进行说明: iterrows(): 按行遍历,将DataFrame的每一行迭代为(index, Series)对,可以通过row[name]对元素进行访问。 itertuples(): 按行遍历,将DataFrame的每一行迭代为元…...

Api接口如何防止被刷?

现在越来越多的应用程序和服务都提供了API接口,使得开发人员可以方便地与这些应用程序和服务进行交互。但是,由于API接口是公开的,因此很容易被黑客利用,对系统造成损害。为了确保API接口的安全性,我们需要采取一些措施…...

Django——orm模块创建表关系

django orm中如何创建表关系 1. 表关系分析 表与表之间的关系: 一对多 多对多 一对一 没有关系 判断表关系的方法: 换位思考用4张表举例: 图书表 出版社表 作者表 作者详情表图书和出版社是一对多的关系 外键字段建在多的那一方图书和作者是多对多的关系 需要创建第三张表来…...

Django知识点

目录 一、三板斧的使用 二、全局配置文件 三、静态文件的设置 四、request对象的方法 五、pycharm链接数据库 六、Django链接数据库 七、orm介绍 一、三板斧的使用 三个方法: HttpResponse renderredirect def index(request):print(request)return HttpR…...

基于单片机设计的智能风扇(红外线无线控制开关调速定时)

一、项目介绍 在炎热的夏季,风扇成为人们室内生活中必不可少的电器产品。然而,传统的风扇控制方式存在一些不便之处,比如需要手动操作开关、无法远程控制和调速,以及缺乏定时功能等。为了解决这些问题,设计了一款基于…...

k8s报错pause 3.2 解决方案

报错 Failed to create pod sandbox: rpc error: code Unknown desc failed to get sandbox image "k8s.gcr.io/pause:3.2": failed to pull image "k8s.gcr.io/pause:3.2": failed to pull and unpack image "k8s.gcr.io/pause:3.2": failed…...

基于遗传算法的电器分类,基于GA的电器分类

目录 背影 遗传算法的原理及步骤 基本定义 编码方式 适应度函数 运算过程 代码 结果分析 展望 完整代码下载链接:基于遗传算法的电器识别,基于遗传算法的电器分类(代码完整,数据齐全)资源-CSDN文库 https://download.csdn.net/download/abc991835105/88506649 背影 电器…...

某XX自考小程序的AES加密分析

前言 主要是报了自考在这个小程序上面做题,就研究了一下这个接口本文仅供学习交流使用,请勿随意传播。如有侵犯你的权益及时联系我删除。 一、抓包分析打开小程序,打开devtools 工具,这里就不啰嗦,直接上过程。 点击…...

加密算法笔记

MD5: 目前相对容易破解,且容易发生碰撞; 不可逆(但是网络上有很多破解方法) SHA256: 不会碰撞; 可以加盐,也可以不加盐; 不可逆; HmacSHA256在线加密-HmacMD5加密-散列哈希HASH加密工具 相同点 1、都是密码散列函数,加密不可逆。 2、都可…...

Sa-Token拦截全部接口必须登录-然后自定义注解来匿名登录-作为权限框架支持,并且同时使用了注解和路由的拦截器模式,此部分的配置如下:

场景 在博主目前正在开发的项目中&#xff0c;就采用了Sa-Token作为权限框架支持&#xff0c;并且同时使用了注解和路由的拦截器模式&#xff0c;此部分的配置如下&#xff1a; Override public void addInterceptors(InterceptorRegistry registry) { List<String> n…...

公司企业端口映射

文章目录 前言如何进行内网映射打开路由器管理网址设置端口映射 前言 公司为了提供稳定的网络服务&#xff0c;需要拥有一个稳定的IPV4的网络地址&#xff0c;一般公司有点规模的&#xff0c;会去电信局里面拉一根专门的网线&#xff0c;获取稳定的全球IPV4地址。 一个IPV4地…...

gitlab安装和使用

gitlab安装和使用 1.下载必要的依赖 sudo yum install curl openssh-server openssh-clients postfix cronie #需要执行sudo service postfix start #配置邮箱的sudo chkconfig postfix on #配置邮箱的#这句是用来做防火墙的&#xff0c;避免用户通过ssh方式和http来访问。su…...

【论文阅读】DALL·E: Zero-Shot Text-to-Image Generation

OpenAI第一代文本生成图片模型 paper&#xff1a;https://arxiv.org/abs/2102.12092 DALLE有120亿参数&#xff0c;基于自回归transformer&#xff0c;在2.5亿 图片-文本对上训练的。实现了高质量可控的text to image&#xff0c;同时也有zero-shot的能力。 DALL-E没有使用扩…...

说一下 toRef、toRefs,以及他们的区别

toRef&#xff1a;创建一个新的Ref变量&#xff0c;转换Reactive对象的某个字段为Ref变量 toRefs&#xff1a;创建一个新的对象&#xff0c;它的每个字段都是Reactive对象各个字段的Ref变量 说一下toRef 先定义一个reactive对象 interface Member {id: numbername: string } c…...

修改Android Studio默认的gradle目录

今天看了一下&#xff0c;gradle在C盘占用了40多G。我C盘是做GHOST的&#xff0c;放在这里不方便。所以就要修改。 新建目录名&#xff08;似乎无必要&#xff09; ANDROID_SDK_HOMEG:\SOFTWARES\android-sdk GRADLE_USER_HOMEG:\SOFTWARES\.gradle 修改目录 File->Setti…...

鲁大师电动车智能化测评报告第二十三期:实测续航95km,九号Q90兼顾个性与实用

鲁大师第二十三期智能化电动车测评排行榜数据来源于鲁大师智慧实验室,测评的车型均为市面上主流品牌的主流车型。截止目前,鲁大师智能化电动车测评的车型高达130余台,且还在不断增加和丰富中。 一、测评依据 鲁大师电动车智能化测评体系包含车辆的状态采集与管理硬件系统、车辆…...

初始化项目骨架(Web3项目一实战之一)

暌违将近一年的时光,也该是时候来几个项目实践。要不,当再次翻看 玩以太坊链上项目的必备技能(…solidity之旅X) ,却未曾见有关于 Web3 项目的实战博文,不免让人唏嘘! 其实,在我敲下玩以太坊链上项目的必备技能这些文字时,心中早有了势必要弄出一个Web3的项目(当然,通…...

在opencv OpenCV中打开相机摄像头,用分水岭算法实时实现图像的分割与提取

import cv2 import numpy as np# 定义回调函数 def callback(x):pass# 打开摄像头 cap cv2.VideoCapture(0)# 创建窗口和控件 cv2.namedWindow(image) cv2.createTrackbar(threshold, image, 0, 255, callback)# 初始化参数 bgdModel np.zeros((1, 65), np.float64) fgdModel…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

汽车生产虚拟实训中的技能提升与生产优化​

在制造业蓬勃发展的大背景下&#xff0c;虚拟教学实训宛如一颗璀璨的新星&#xff0c;正发挥着不可或缺且日益凸显的关键作用&#xff0c;源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例&#xff0c;汽车生产线上各类…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信

文章目录 Linux C语言网络编程详细入门教程&#xff1a;如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket&#xff08;服务端和客户端都要&#xff09;2. 绑定本地地址和端口&#x…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...