设计模式-单例模式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连接无法建立的排查和修复
嗨,亲爱的读者朋友们!你是否曾经遇到过PPPoE连接无法建立的问题?今天我将为你详细解析排查和修复这个问题的步骤。 检查物理连接 首先,我们需要确保物理连接没有问题。请按照以下步骤进行检查: - 检查网线是否插好&…...
QT 发布软件基本操作
一、配置环境变量 找到Qt安装时的bin目录的路径:D:\Qt\Qt5.14.2\5.14.2\mingw73_64\bin,将目录拷贝至下述环境变量中。 打开计算机的高级系统设置 选中环境变量-->系统变量-->Path 点击编辑-->新建-->粘贴 二、生成发布软件的可执行程序 …...
CTFhub-SSRF-内网访问
CTFHub 环境实例 | 提示信息 http://challenge-8bf41c5c86a8c5f4.sandbox.ctfhub.com:10800/?url_ 根据提示,在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); pwd)命令详解 运维Shell脚本小试牛刀(四): 多层嵌套if...elif...elif....else fi_蜗牛杨哥的博客-CSDN博客 Cenos7安装小火车程序动画 一:替换…...
Node 执行命令时传参 process.argv
process 对象是一个全局变量,提供当前 Node.js 进程的有关信息,以及控制当前 Node.js 进程。 因为是全局变量,所以无需使用 require()。 process.argv 属性返回一个数组,这个数组包含了启动Node.js进程时的命令行参数,…...
【Vue】快速上手--Vue 3.0
什么是 Vue? Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的…...
PyTorch深度学习遥感影像地物分类与目标检测、分割及遥感影像问题深度学习优化实践技术应用
我国高分辨率对地观测系统重大专项已全面启动,高空间、高光谱、高时间分辨率和宽地面覆盖于一体的全球天空地一体化立体对地观测网逐步形成,将成为保障国家安全的基础性和战略性资源。未来10年全球每天获取的观测数据将超过10PB,遥感大数据时…...
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 解决: 改用其他版本,我没写版本号,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的子项目发展而来,于2010年11月正式成为了Apache的顶级项目。ZooKeeper为分布式应用提供了高效且可靠的分布式协调服务,提供了诸如统一命名服务、配置管理和分布式锁等分布式的基础服务。在解决分布式数据一致性方面&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可能的值
必备知识点:n 在底层是由三条指令在CPU完成的 load : 将内存的值读取到CPU寄存器add : 将CPU寄存器中的值进行1操作save : 将CPU寄存器中的值写回内容 回答 首先n操作在底层是由三条指令在CPU完成的,先要将内存中n的值读取到CPU寄存器,然后…...
Jtti:如何通过宝塔面板快速安装WordPress博客源码?
通过宝塔面板快速安装WordPress博客源码是非常简单的。宝塔面板提供了图形化界面,使安装过程变得直观和方便。以下是通过宝塔面板安装WordPress的步骤: 登录宝塔面板: 打开您的Web浏览器,访问您的宝塔面板地址(通常是 …...
Windows右键添加用 VSCODE 打开
1.安装VSCODE时 安装时会有个选项来添加,如下: ①将“通过code 打开“操作添加到windows资源管理器文件上下文菜单 ②将“通过code 打开”操作添加到windows资源管理器目录上下文菜单 说明:①②勾选上,可以对文件,目…...
达梦数据库管理用户和创建用户介绍
概述 本文主要对达梦数据库管理用户和创建用户进行介绍和总结。 1.管理用户介绍 1.1 达梦安全机制 任何数据库设计和使用都需要考虑安全机制,达梦数据库采用“三权分立”或“四权分立”的安全机制,将系统中所有的权限按照类型进行划分,为每…...
使用python,生成数字在图片上的验证码
许多网站在注册时都要求输入验证码,这样做为了防止被程序恶意注册和保证网站安全 1. Pillow PIL(Python Imaging Library)是一个强大的python图像处理库,只是支持到python2.7, Pillow虽说是PIL的一个分支,但是pillow支持python3.xÿ…...
阿晨的运维笔记 | 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报…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...
用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...
【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
