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

设计模式 - Singleton pattern 单例模式

文章目录

  • 定义
  • 单例模式的实现构成
    • 构成
    • UML图
  • 单例模式的六种实现
    • 懒汉式-线程不安全
    • 懒汉式-线程安全
    • 饿汉式-线程安全
    • 双重校验锁-线程安全
    • 静态内部类实现
    • 枚举实现
  • 总结
  • 其他设计模式文章:
  • 最后

定义

在这里插入图片描述

单例模式是一种创建型设计模式,它用来保证一个类只有一个实例, 并且提供一个访问该实例的全局节点。其在很多场景中都有应用,比如数据库连接池、日志记录器、Spring中对象的创建等。

总的来说,单例模式在需要控制实例数量、确保全局唯一性的场景中被广泛应用。单例模式通过限制类的实例化对象为一个,可以确保全局唯一性的场景中被广泛应用,从而有助于控制资源访问、简化全局访问点、减少内存占用等,在很多情况下都可以提升程序的运行效率。

单例模式的实现构成

构成

一个私有的构造函数、一个私有的静态变量以及一个共有的静态函数。

其中,私有构造函数保证了其他线程不能通过new来创建对象实例,而共有的静态函数则是用来后续所有对此函数的调用都返回唯一的私有静态变量。

UML图

在这里插入图片描述

单例模式的六种实现

懒汉式-线程不安全

下面实现中,instance 被延迟实例化,这样的话,当没有使用到这个类的话,就会节约资源,不会实例化 LazySingletonsAreNotSafe

但是该实现是线程不安全的,因为在多线程环境下,可以有多个线程同时进入 getInstance 方法,并且这个时候 instance 还未实例化,那么它们就都可以进入到 if 逻辑中,执行实例化操作,从而导致线程不安全问题。

public class LazySingletonsAreNotSafe {private static LazySingletonsAreNotSafe instance;private LazySingletonsAreNotSafe() {}public static LazySingletonsAreNotSafe getInstance() {if (instance == null) {instance = new LazySingletonsAreNotSafe();}return instance;}
}

懒汉式-线程安全

那么,如何可以保证线程安全呢?

其实,上一个实现方式中,线程不安全就是因为 instance 的实例化被执行了很多次,所以我们只要对 getInstance 方法进行加锁,保证同一个时间点只有一个线程可以进入该方法进行实例化操作,那么就保证了线程安全问题。
实现代码如下:

public class LazySingletonsAreSafe {private static LazySingletonsAreSafe instance;private LazySingletonsAreSafe() {}// 关键点:synchronized进行了加锁操作,从而保证线程安全。public static synchronized LazySingletonsAreSafe getInstance() {if (instance == null) {instance = new LazySingletonsAreSafe();}return instance;}
}

饿汉式-线程安全

对于懒汉式方法,如果不加锁会导致线程安全问题,而加锁虽然会保证线程安全,但是也带来了一定程度上的性能损耗,因此可以采用饿汉式。
懒汉式线程安全问题的原因是 getInstance 方法可能被执行多次,从而导致被实例化多次。所以我们采用在类加载的时候,直接实例化 instance ,这样就会避免实例化多次的问题。

当然,因为我们一开始在类加载的时候对象就被实例化了,所以也不会有延迟实例化种可以节约资源的优点。

public class EagerSingleton {private static final EagerSingleton instance = new EagerSingleton();private EagerSingleton() {}public static EagerSingleton getInstance() {return instance;}
}

双重校验锁-线程安全

双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。

public class DoubleCheckedLockingSingleton {// 注意:volatile 修饰private static volatile DoubleCheckedLockingSingleton instance;private DoubleCheckedLockingSingleton() {}public static DoubleCheckedLockingSingleton getInstance() {if (instance == null) {synchronized (DoubleCheckedLockingSingleton.class) {if (instance == null) {instance = new DoubleCheckedLockingSingleton();}}}return instance;}
}

问题1: 为什么两个if?

if (instance == null) {synchronized (DoubleCheckedLockingSingleton.class) {if (instance == null) {instance = new DoubleCheckedLockingSingleton();}}}

第一个if是因为:高并发场景下,还是可能有不止一个线程成功的在 instance 还未初始化的时候就进入这里了,所以他们都会走下面的逻辑,所以加了一把锁,用来保证线程安全问题。
而第二个if则是因为:等到第一个线程执行完实例化之后,它会释放锁,这样的话下一个线程就会来拿这把锁,然后进行新一轮的实例化。所以,在锁里添加了第二个if用来进行判断,避免实例化多次。

问题2: 为什么 instancevolatile 进行修饰?

private static volatile DoubleCheckedLockingSingleton instance;

这个是因为 volatile 有禁止指令重排的功能。上述代码中单例对象有的时候可能会发生空指针异常的问题。

对于instance = new DoubleCheckedLockingSingleton(); 它其实是分为三个步骤来执行的:

  1. JVM为对象分配内存
  2. 在内存中进行对象的初始化
  3. 将内存对应的地址复制给instance

假设,现在有两个线程进入到了getInstance方法,当T1线程执行实例化操作时,T2线程在进行判断。

因为instance = new DoubleCheckedLockingSingleton();操作不是原子的,所以编译器可能会进行指令的重排序,即:

  1. JVM为对象分配内存
  2. 将内存对应的地址复制给instance
  3. 在内存中进行对象的初始化

这样的话,当T1线程执行完第二步地址复制给instance的时候,T2线程去进行判断,那么instance == null则是为true,所以会直接跳到最下面 return instance。从而导致空指针问题。

volatile可以避免指令重排,所以只要用volatile修饰instance就可以避免这个问题了。
在这里插入图片描述

静态内部类实现

BillPughSingleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getUniqueInstance 方法从而触发 SingletonHolder.INSTANCESingletonHolder才会被加载,进行初始化。

public class BillPughSingleton {private BillPughSingleton() {}private static class SingletonHelper {private static final BillPughSingleton INSTANCE = new BillPughSingleton();}public static BillPughSingleton getInstance() {return SingletonHelper.INSTANCE;}
}

枚举实现

枚举实例的创建是线程安全的,而且在任何情况下都是它一个单例。在别的几种单例中,反序列化时会重新创建对象,而枚举单例则不存在这种情况。

public enum EnumSingleton {INSTANCE;public void someMethod() {}
}

总结

      
1. 饿汉式

    实现:在类加载时就完成了实例化。
    特点:线程安全,实现简单;但可能会造成资源浪费,因为即使不需要使用实例,也会在类加载时创建。
      
2. 懒汉式

    实现:在第一次调用 getInstance() 方法时进行实例化。
    特点:延迟加载,节省资源;但需要在 getInstance() 方法上加锁才可以保证线程安全,会影响性能。
      
3. 双重校验锁

    实现:在 getInstance() 方法中加入两次实例检查,第二次检查前加上锁,既保证了线程安全又提高了效率。
    特点:结合了懒汉式和饿汉式的优点,既实现了延迟加载,又优化了并发性能。
      
4. 静态内部类

    实现:将单例实例放在静态内部类中,当外部类被加载时静态内部类并不会被加载,只有在首次调用 getInstance() 方法时才会加载。
    特点:既实现了延迟加载,又保证了线程安全,且不需显式同步。
      
5. 枚举

    实现:利用枚举类型的特性来保证实例的唯一性。
    特点:线程安全,简洁易读,还能防止反序列化攻击。

其他设计模式文章:

  • 设计模式 - Singleton pattern 单例模式
  • 设计模式 - Factory Method 工厂方法
  • 设计模式 - Chain Of Responsibility 责任链模式
  • 设计模式 - Template Method 模板方法
  • 设计模式 - Strategy Pattern策略模式
  • 设计模式 - Observer Pattern 观察者模式

最后

如果小伙伴们觉得我写的文章不错的话,那么请给我点点关注,我们下次见!
      在这里插入图片描述

相关文章:

设计模式 - Singleton pattern 单例模式

文章目录 定义单例模式的实现构成构成UML图 单例模式的六种实现懒汉式-线程不安全懒汉式-线程安全饿汉式-线程安全双重校验锁-线程安全静态内部类实现枚举实现 总结其他设计模式文章:最后 定义 单例模式是一种创建型设计模式,它用来保证一个类只有一个实…...

显示学习5(基于树莓派Pico) -- 彩色LCD的驱动

和这篇也算是姊妹篇,只是一个侧重SPI协议,一个侧重显示驱动。 总线学习3--SPI-CSDN博客 驱动来自:https://github.com/boochow/MicroPython-ST7735 所以这里主要还是学习。 代码Init def __init__( self, spi, aDC, aReset, aCS) :"&…...

ros vscode配置gdb调试

ros工程vscode下配置gdb的调试环境需要添加几个配置文件,下面贴一下用得到的几个配置文件。 c_cpp_properties.json,这个配置作用是方便代码跳转。 {"configurations": [{"browse": {"databaseFilename": "${defau…...

C 环境设置

C 环境设置 C语言作为一种广泛使用的编程语言,其环境设置是每个开发者必须掌握的基本技能。本文将详细介绍如何在不同的操作系统上设置C语言开发环境,包括Windows、macOS和Linux系统。我们将涵盖安装编译器、配置开发环境以及编写和运行第一个C程序。 Windows系统上的C环境…...

Linux-ubuntu操作系统装机步骤

1、下载iso镜像 方法一、访问Ubuntu官网 方法二、163镜像 2、制作U盘启动盘 方法一、UltraISO(软碟通)写入硬盘映像,参考该 [链接] 方法二、Rufus,参考该 [链接] 3、安装 参考该 [链接] 4、相关配置 Ubuntu 换源 参考链接…...

马尔科夫毯:信息屏障与状态独立性的守护者

马尔科夫毯(Markov Blanket)是概率图模型中的一个重要概念,用于描述某一节点在网络中的信息独立性和条件依赖关系。马尔科夫毯定义了一个节点的“信息屏障”,即给定马尔科夫毯中节点的状态,该节点与网络中其他节点的状…...

Pandas的30个高频函数使用介绍

Pandas是Python中用于数据分析的一个强大的库,它提供了许多功能丰富的函数。本文介绍其中高频使用的30个函数。 read_csv(): 从CSV文件中读取数据并创建DataFrame对象。 import pandas as pd df pd.read_csv(data.csv) read_excel(): 从Excel文件中读取数据…...

1. protobuf学习

文章目录 1. protobuf介绍1.1 ProtoBuf使用场景说明2. 其他序列化介绍2.1 Json2.1.1 使用Json序列化2.1.2 Json反序列化2.2 其他可选地序列化和反序列化3. protoBuf3.1 protobuf数据类型3.2 protobuf使用步骤3.2.1 定义proto文件3.2.2 编译proto文件3.2.2.1 安装protocol buffe…...

Java面试题:SpringBean的生命周期

SpringBean的生命周期 BeanDefinition Spring容器在进行实例化时,会将xml配置的信息封装成BeanDefinition对象 Spring根据BeanDefinition来创建Bean对象 包含很多属性来描述Bean 包括 beanClassName:bean的类名,通过类名进行反射 initMethodName:初始化方法名称 proper…...

50 IRF检测MAD-BFD

IRF 检测MAD-BFD IRF配置思路 网络括谱图 主 Ten-GigabitEthernet 1/0/49 Ten-GigabitEthernet 1/0/50 Ten-GigabitEthernet 1/0/51 备 Ten-GigabitEthernet 2/0/49 Ten-GigabitEthernet 2/0/50 Ten-GigabitEthernet 2/0/51 1 利用console线进入设备的命令行页…...

SpringSecurity-1(认证和授权+SpringSecurity入门案例+自定义认证+数据库认证)

SpringSecurity 1 初识权限管理1.1 权限管理的概念1.2 权限管理的三个对象1.3 什么是SpringSecurity 2 SpringSecurity第一个入门程序2.1 SpringSecurity需要的依赖2.2 创建web工程2.2.1 使用maven构建web项目2.2.2 配置web.xml2.2.3 创建springSecurity.xml2.2.4 加载springSe…...

Java高级

类变量/静态变量package com.study.static_; 通过static关键词声明,是该类所有对象共享的对象,任何一个该类的对象去访问他的时候,取到的都是相同的词,同样任何一个该类的对象去修改,所修改的也是同一个对象. 如何定义及访问? 遵循相关访问权限 访问修饰符 static 数据类型…...

python实现图像分割算法3

python实现区域增长算法 算法原理基本步骤数学模型Python实现详细解释优缺点应用领域区域增长算法是一种经典的图像分割技术,它的目标是将图像划分为多个互不重叠的区域。该算法通过迭代地合并与种子区域相似的邻域像素来实现分割。区域增长算法通常用于需要精确分割的场景,如…...

解密XXE漏洞:原理剖析、复现与代码审计实战

在网络安全领域,XML外部实体(XXE)漏洞因其隐蔽性和危害性而备受关注。随着企业对XML技术的广泛应用,XXE漏洞也逐渐成为攻击者们利用的重点目标。一个看似无害的XML文件,可能成为攻击者入侵系统的利器。因此&#xff0c…...

Spring Boot集成Resilience4J实现限流/重试/隔离

1.前言 上篇文章讲了Resilience4J实现熔断功能&#xff0c;文章详见&#xff1a;Spring Boot集成Resilience4J实现断路器功能 | Harries Blog™&#xff0c;本篇文章主要讲述基于Resilience4J实现限流/重试/隔离。 2.代码工程 pom.xml <dependency><groupId>io…...

谷粒商城实战笔记-119~121-全文检索-ElasticSearch-mapping

文章目录 一&#xff0c;119-全文检索-ElasticSearch-映射-mapping创建1&#xff0c;Elasticsearch7开始不支持类型type。2&#xff0c;mapping2.1 Elasticsearch的Mapping 二&#xff0c;120-全文检索-ElasticSearch-映射-添加新的字段映射三&#xff0c;121-全文检索-Elastic…...

Java 并发编程:Java 线程池的介绍与使用

大家好&#xff0c;我是栗筝i&#xff0c;这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 024 篇文章&#xff0c;在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验&#xff0c;并希望进…...

ubuntu上安装HBase伪分布式-2024年08月04日

ubuntu上安装HBase伪分布式-2024年08月04日 1.HBase介绍2.HBase与Hadoop的关系3.安装前言4.下载及安装5.单机配置6.伪分布式配置 1.HBase介绍 HBase是一个开源的非关系型数据库&#xff0c;它基于Google的Bigtable设计&#xff0c;用于支持对大型数据集的实时读写访问。HBase有…...

Mojo的特征与参数(参数化部分)详解

许多语言都具有元编程功能:即编写生成或修改代码的代码。Python 具有动态元编程功能:装饰器、元类等功能。这些功能使 Python 非常灵活且高效,但由于它们是动态的,因此会产生运行时开销。其他语言具有静态或编译时元编程功能,如 C 预处理器宏和 C++ 模板。这些功能可能受到…...

C++数组、vector求最大值最小值及其下标

使用 <algorithm> 头文件来查找数组或向量中最大值、最小值及其索引 #include <iostream> #include <vector> #include <algorithm> // 包含 std::max_element 和 std::min_elementint main() {std::vector<int> vec {3, 1, 4, 2, 5};// 查找最…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析

今天聊的内容&#xff0c;我认为是AI开发里面非常重要的内容。它在AI开发里无处不在&#xff0c;当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗"&#xff0c;或者让翻译模型 "将这段合同翻译成商务日语" 时&#xff0c;输入的这句话就是 Prompt。…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业

6月9日&#xff0c;国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解&#xff0c;“超级…...

04-初识css

一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

腾讯云V3签名

想要接入腾讯云的Api&#xff0c;必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口&#xff0c;但总是卡在签名这一步&#xff0c;最后放弃选择SDK&#xff0c;这次终于自己代码实现。 可能腾讯云翻新了接口文档&#xff0c;现在阅读起来&#xff0c;清晰了很多&…...