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

设计模式-单例模式Singleton

单例模式

  • 单例模式 (Singleton) (重点)
    • 1) 为什么要使用单例
    • 2) 如何实现一个单例
      • 2.a) 饿汉式
      • 2.b) 懒汉式
      • 2.c) 双重检查锁
      • 2.d) 静态内部类
      • 2.e) 枚举类
      • 2.f) 反射入侵
      • 2.g) 序列化与反序列化安全
    • 3) 单例存在的问题
      • 3.a) 无法支持面向对象编程

单例模式 (Singleton) (重点)

一个类只允许创建一个对象(或者实例),那这个类就是一个单例类

1) 为什么要使用单例

1.表示全局唯一

如果有些数据在系统中应该且只能保存一份,那就应该设计为单例类:

  • 配置类:在系统中,我们只有一个配置文件,当配置文件被加载到内存之后,应该被映射为一个唯一的【配置实例】
  • 全局计数器:我们使用一个全局的计数器进行数据统计、生成全局递增ID等功能。若计数器不唯一,很有可能产生统计无效,ID重复等

2.处理资源访问冲突

如果使用单个实例输出日志,锁【this】即可。

如果要保证JVM级别防止日志文件访问冲突,锁【class】即可。

如果要保证集群服务级别的防止日志文件访问冲突,加分布式锁即可

2) 如何实现一个单例

常见的单例设计模式,有如下五种写法,在编写单例代码的时候要注意以下几点:

  • 1.构造器需要私有化
  • 2.暴露一个公共的获取单例对象的接口
  • 3.是否支持懒加载(延迟加载)
  • 4.是否线程安全

2.a) 饿汉式

在类加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的

/*** 饿汉式单例的实现*  - 不支持懒加载*  - jvm保证线程安全*/
public class EagerSingleton {/*** 当启动程序的时候,就创建这个实例*/// 1.持有一个jvm全局唯一的实例private static final EagerSingleton instance = new EagerSingleton();// 2.为了避免别人随意的创建,需要私有化构造器private EagerSingleton() {}// 3.暴露一个方法,用来获取实例public static EagerSingleton getInstance() {return instance;}
}

2.b) 懒汉式

懒汉式相对于饿汉式的优势是支持延迟加载,具体的代码实现如下所示:

支持延迟加载

/*** 懒汉式单例的实现*  - 支持懒加载*/
public class LazySingleton {/*** 当需要使用这个实例的时候,再创建这个实例*/// 1.持有一个jvm全局唯一的实例private static LazySingleton instance;// 2.为了避免别人随意的创建,需要私有化构造器private LazySingleton() {}// 3.暴露一个方法,用来获取实例// - 懒加载-线程不安全,因为当面对大量并发请求时,有可能会有超过一个线程同时执行此方法,是无法保证其单例的特点// - 加锁:使用 synchronized,(对.class加锁) 但是方法上加锁会极大的降低获取单例对象的并发度public static LazySingleton getInstance() {if (instance == null) {instance = new LazySingleton();}return instance;}
}

2.c) 双重检查锁

饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。既支持延迟加载、又支持高并发的单例实现方式,也就是双重检测锁:

/*** 双重检查锁单例的实现*/
public class DoubleCheckLockSingleton {// 1.持有一个jvm全局唯一的实例// - 因为创建对象不是一个原子性操作,即使使用双重检查锁,也可能在创建过程中产生半初始化状态// - volatile 1.保证内存可见 2.保存有序性// - jdk1.9以上,不加volatile也可以,jvm内部处理有序性private static volatile DoubleCheckLockSingleton instance;// 2.为了避免别人随意的创建,需要私有化构造器private DoubleCheckLockSingleton() {}// 3.暴露一个方法,用来获取实例// - 第一次创建需要上锁,一旦创建完成,就不再需要上锁// - 事实上获取单例并没有线程安全的问题public static DoubleCheckLockSingleton getInstance() {if (instance == null) {synchronized (DoubleCheckLockSingleton.class) {// 创建if (instance == null) {instance = new DoubleCheckLockSingleton();}}}return instance;}
}

2.d) 静态内部类

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

当外部类 InnerSingleton()被加载的时候,并不会创建 InnerSingleton的实例对象。只有当调用 getInstance() 方法时,InnerSingletonHolder 才会被加载,这个时候才会创建 instance实例。

/*** 静态内部类的方式实现单例*/
public class InnerSingleton {// 1.私有化构造器private InnerSingleton() {}// 2.提供一个方法,获取单例对象public InnerSingleton getInstance() {return InnerSingletonHolder.instance;}// 3.定义内部类,来持有实例// - 特性:类加载的时机 --> 一个类会在第一次使用的时候被加载// - 实例会在内部类加载(调用getInstance()方法之后)会创建private static class InnerSingletonHolder {private static final InnerSingleton instance = new InnerSingleton();}}

2.e) 枚举类

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

/*** 枚举:累加器*/
public enum GlobalCounter {// 这个INSTANCE是一个单例// 对于枚举类。任何一个枚举项就是一个单例// 本质上就是 static final GlobalCounter instance = new GlobalCounter()INSTANCE;private AtomicLong atomicLong = new AtomicLong(0);public Long getNumber() {return atomicLong.getAndIncrement();}
}

2.f) 反射入侵

事实上,我们想要阻止其他人构造实例仅仅私有化构造器还是不够的,因为我们还可以使用反射获取私有构造器进行构造,当然使用枚举的方式是可以解决这个问题的,对于其他的书写方案,我们通过下边的方式解决:

// 反射代码
Class<DoubleCheckLockSingleton> instance = DoubleCheckLockSingleton.class;
Constructor<DoubleCheckLockSingleton> constructor = instance.getDeclaredConstructor();
constructor.setAccessible(true);boolean flag = DoubleCheckLockSingleton.getInstance() == constructor.newInstance();
log.info("flag -> {}",flag);
/*** 单例的防止反射入侵的代码实现*/
public class ReflectSingleton {/*** 可以使用反射获取私有构造器进行构造*/private static volatile ReflectSingleton instance;// 为了避免别人随意的创建,需要私有化构造器private ReflectSingleton() {// 升级版本 --> 不要让人使用反射创建if (instance != null) {throw new RuntimeException("该对象是单例,无法创建多个");}}public static ReflectSingleton getInstance() {if (instance == null) {synchronized (ReflectSingleton.class) {// 创建if (instance == null) {instance = new ReflectSingleton();}}}return instance;}
}

2.g) 序列化与反序列化安全

事实上,到目前为止,我们的单例依然是有漏洞的

/*** 通过序列化*/
@Test
public void testSerialize() throws Exception {// 获取单例并序列化SerializableSingleton instance = SerializableSingleton.getInstance();FileOutputStream fout = new FileOutputStream("F://singleton.txt");ObjectOutputStream out = new ObjectOutputStream(fout);out.writeObject(instance);// 将实例反序列化出来FileInputStream fin = new FileInputStream("F://singleton.txt");ObjectInputStream in = new ObjectInputStream(fin);Object o = in.readObject();log.info("是同一个实例吗 {}", o == instance); // 是同一个实例吗 false
}

在进行反序列化时,会尝试执行readResolve方法,并将返回值作为反序列化的结果,而不会克隆一个新的实例,保证jvm中仅仅有一个实例存在

public class Singleton implements Serializable {// 省略其他的内容public static Singleton getInstance() {}// 需要加这么一个方法public Object readResolve(){return singleton;}
}

3) 单例存在的问题

在项目中使用单例,都是用它来表示一些全局唯一类,比如配置信息类、连接池类、ID 生成器类。单例模式书写简洁、使用方便,在代码中,我们不需要创建对象。但是,这种使用方法有点类似硬编码(hard code),会带来诸多问题,所以我们一般会使用spring的单例容器作为替代方案。

3.a) 无法支持面向对象编程

OOP 的三大特性是封装、继承、多态。单例将构造私有化,直接导致的结果就是,他无法成为其他类的父类,这就相当于直接放弃了继承和多态的特性,也就相当于损失了可以应对未来需求变化的扩展性,以后一旦有扩展需求,比如写一个类似的具有绝大部分相同功能的单例,我们不得不新建一个十分【雷同】的单例。

相关文章:

设计模式-单例模式Singleton

单例模式 单例模式 (Singleton) (重点)1) 为什么要使用单例2) 如何实现一个单例2.a) 饿汉式2.b) 懒汉式2.c) 双重检查锁2.d) 静态内部类2.e) 枚举类2.f) 反射入侵2.g) 序列化与反序列化安全 3) 单例存在的问题3.a) 无法支持面向对象编程 单例模式 (Singleton) (重点) 一个类只…...

PPPoE连接无法建立的排查和修复

嗨&#xff0c;亲爱的读者朋友们&#xff01;你是否曾经遇到过PPPoE连接无法建立的问题&#xff1f;今天我将为你详细解析排查和修复这个问题的步骤。 检查物理连接 首先&#xff0c;我们需要确保物理连接没有问题。请按照以下步骤进行检查&#xff1a; - 检查网线是否插好&…...

QT 发布软件基本操作

一、配置环境变量 找到Qt安装时的bin目录的路径&#xff1a;D:\Qt\Qt5.14.2\5.14.2\mingw73_64\bin&#xff0c;将目录拷贝至下述环境变量中。 打开计算机的高级系统设置 选中环境变量-->系统变量-->Path 点击编辑-->新建-->粘贴 二、生成发布软件的可执行程序 …...

CTFhub-SSRF-内网访问

CTFHub 环境实例 | 提示信息 http://challenge-8bf41c5c86a8c5f4.sandbox.ctfhub.com:10800/?url_ 根据提示&#xff0c;在url 后门添加 127.0.0.1/flag.php http://challenge-8bf41c5c86a8c5f4.sandbox.ctfhub.com:10800/?url127.0.0.1/flag.php ctfhub{a6bb51530c8f6be0…...

Cenos7安装小火车程序动画

运维Shell脚本小试牛刀(一) 运维Shell脚本小试牛刀(二) 运维Shell脚本小试牛刀(三)::$(cd $(dirname $0)&#xff1b; pwd)命令详解 运维Shell脚本小试牛刀(四): 多层嵌套if...elif...elif....else fi_蜗牛杨哥的博客-CSDN博客 Cenos7安装小火车程序动画 一&#xff1a;替换…...

Node 执行命令时传参 process.argv

process 对象是一个全局变量&#xff0c;提供当前 Node.js 进程的有关信息&#xff0c;以及控制当前 Node.js 进程。 因为是全局变量&#xff0c;所以无需使用 require()。 process.argv 属性返回一个数组&#xff0c;这个数组包含了启动Node.js进程时的命令行参数&#xff0c…...

【Vue】快速上手--Vue 3.0

什么是 Vue&#xff1f;​ Vue (发音为 /vjuː/&#xff0c;类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助你高效地开发用户界面。无论是简单还是复杂的…...

PyTorch深度学习遥感影像地物分类与目标检测、分割及遥感影像问题深度学习优化实践技术应用

我国高分辨率对地观测系统重大专项已全面启动&#xff0c;高空间、高光谱、高时间分辨率和宽地面覆盖于一体的全球天空地一体化立体对地观测网逐步形成&#xff0c;将成为保障国家安全的基础性和战略性资源。未来10年全球每天获取的观测数据将超过10PB&#xff0c;遥感大数据时…...

04、添加 com.fasterxml.jackson.dataformat -- jackson-dataformat-xml 依赖报错

Correct the classpath of your application so that it contains a single, compatible version of com.fasterxml.jackson.dataformat.xml.XmlMapper 解决&#xff1a; 改用其他版本&#xff0c;我没写版本号&#xff0c;springboot自己默认的是 2.11.4 版本 成功启动项目…...

禅道项目管理系统 - 操作使用 (2023版)

1. 部门-用户-权限 新增部门 新增用户 设置权限 2. 项目集创建 项目集 - 添加项目集 3. 产品线创建 产品 - 产品线 4. 产品创建 产品 - 产品列表 - 添加产品 5. 产品计划创建 产品 - xx产品 - 计划 - 创建计划 我这里创建3个计划 (一期, 二期, 三期) 6. 研发需求 - 创建模块…...

C++的多重继承

派生类都只有一个基类,称为单继承(Single Inheritance)。除此之外,C++也支持多继承(Multiple Inheritance),即一个派生类可以有两个或多个基类。 多继承容易让代码逻辑复杂、思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java、C#、PHP 等干脆取消了多继承。 …...

ZooKeeper与Paxos

Apache ZooKeeper是由Apache Hadoop的子项目发展而来&#xff0c;于2010年11月正式成为了Apache的顶级项目。ZooKeeper为分布式应用提供了高效且可靠的分布式协调服务&#xff0c;提供了诸如统一命名服务、配置管理和分布式锁等分布式的基础服务。在解决分布式数据一致性方面&a…...

Cargo 静态编译

git clone --recursive https://github.com/kornelski/pngquant.git vi ~/.cargo/config.toml[http] debug true proxy "127.0.0.1:1080" 1.apt 更新 2.apt install cargo 3.修改源码的Cargo.toml [source.crates-io] #registry "https://code.aliyun.com…...

【多线程】有两个线程都能访问n,初始时n为0,⼀个线程执⾏n++,n+=2,另⼀个线程执⾏n+=3,当两个线程都执行完后n可能的值

必备知识点&#xff1a;n 在底层是由三条指令在CPU完成的 load : 将内存的值读取到CPU寄存器add : 将CPU寄存器中的值进行1操作save : 将CPU寄存器中的值写回内容 回答 首先n操作在底层是由三条指令在CPU完成的&#xff0c;先要将内存中n的值读取到CPU寄存器&#xff0c;然后…...

Jtti:如何通过宝塔面板快速安装WordPress博客源码?

通过宝塔面板快速安装WordPress博客源码是非常简单的。宝塔面板提供了图形化界面&#xff0c;使安装过程变得直观和方便。以下是通过宝塔面板安装WordPress的步骤&#xff1a; 登录宝塔面板&#xff1a; 打开您的Web浏览器&#xff0c;访问您的宝塔面板地址&#xff08;通常是 …...

Windows右键添加用 VSCODE 打开

1.安装VSCODE时 安装时会有个选项来添加&#xff0c;如下&#xff1a; ①将“通过code 打开“操作添加到windows资源管理器文件上下文菜单 ②将“通过code 打开”操作添加到windows资源管理器目录上下文菜单 说明&#xff1a;①②勾选上&#xff0c;可以对文件&#xff0c;目…...

达梦数据库管理用户和创建用户介绍

概述 本文主要对达梦数据库管理用户和创建用户进行介绍和总结。 1.管理用户介绍 1.1 达梦安全机制 任何数据库设计和使用都需要考虑安全机制&#xff0c;达梦数据库采用“三权分立”或“四权分立”的安全机制&#xff0c;将系统中所有的权限按照类型进行划分&#xff0c;为每…...

使用python,生成数字在图片上的验证码

许多网站在注册时都要求输入验证码&#xff0c;这样做为了防止被程序恶意注册和保证网站安全 1. Pillow PIL(Python Imaging Library)是一个强大的python图像处理库&#xff0c;只是支持到python2.7, Pillow虽说是PIL的一个分支&#xff0c;但是pillow支持python3.x&#xff…...

阿晨的运维笔记 | CentOS部署Docker

使用yum安装 # step 1: 安装必要的一些系统工具 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 # Step 2: 添加软件源信息 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # Step 3: 更新并安装 …...

自动化运维:Ansible基础与命令行模块操作

目录 一、理论 1. Ansible 2.部署Ansible自动化运维工具 3.Ansible常用模块 4.hostsinverntory主机清单 二、实验 1.部署Ansible自动化运维工具 2.ansible 命令行模块 3.hostsinverntory主机清单 三、问题 1. ansible远程shell失败 2.组变量查看webservers内主机ip报…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

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

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

JDK 17 新特性

#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持&#xff0c;不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的&#xff…...

多模态大语言模型arxiv论文略读(108)

CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题&#xff1a;CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者&#xff1a;Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...