当前位置: 首页 > 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};// 查找最…...

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;供调用如何按…...

376. Wiggle Subsequence

376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

《通信之道——从微积分到 5G》读书总结

第1章 绪 论 1.1 这是一本什么样的书 通信技术&#xff0c;说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号&#xff08;调制&#xff09; 把信息从信号中抽取出来&am…...

Axios请求超时重发机制

Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式&#xff1a; 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

分布式增量爬虫实现方案

之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面&#xff0c;避免重复抓取&#xff0c;以节省资源和时间。 在分布式环境下&#xff0c;增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路&#xff1a;将增量判…...

HDFS分布式存储 zookeeper

hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架&#xff0c;允许使用简单的变成模型跨计算机对大型集群进行分布式处理&#xff08;1.海量的数据存储 2.海量数据的计算&#xff09;Hadoop核心组件 hdfs&#xff08;分布式文件存储系统&#xff09;&a…...

论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing

Muffin 论文 现有方法 CRADLE 和 LEMON&#xff0c;依赖模型推理阶段输出进行差分测试&#xff0c;但在训练阶段是不可行的&#xff0c;因为训练阶段直到最后才有固定输出&#xff0c;中间过程是不断变化的。API 库覆盖低&#xff0c;因为各个 API 都是在各种具体场景下使用。…...

wpf在image控件上快速显示内存图像

wpf在image控件上快速显示内存图像https://www.cnblogs.com/haodafeng/p/10431387.html 如果你在寻找能够快速在image控件刷新大图像&#xff08;比如分辨率3000*3000的图像&#xff09;的办法&#xff0c;尤其是想把内存中的裸数据&#xff08;只有图像的数据&#xff0c;不包…...

SQL Server 触发器调用存储过程实现发送 HTTP 请求

文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...

MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释

以Module Federation 插件详为例&#xff0c;Webpack.config.js它可能的配置和含义如下&#xff1a; 前言 Module Federation 的Webpack.config.js核心配置包括&#xff1a; name filename&#xff08;定义应用标识&#xff09; remotes&#xff08;引用远程模块&#xff0…...