每日学习 设计模式 五种不同的单例模式

狮子大佬原文
https://blog.csdn.net/weixin_40461281/article/details/135050977

第一种 饿汉式

为什么叫饿汉,指的是"饿" 也就是说对象实例在程序启动时就已经被创建好,不管你是否需要,它都会在类加载时立即实例化,也就是说 实例化是在类加载时候完成的,早早的吃饱了

   //饿汉单例public class Demo1{private static final Demo1 instance = new Demo1();public Demo1(){}public static Demo1 getInstance(){return instance;}}

优点:执行效率高,性能高,没有任何的锁
缺点:某些情况下,可能会造成内存浪费

第二种 懒汉式

懒汉指的是“懒”也就是说,实例对象的创建是延迟的,只有在第一次调用 getInstance() 方法时,才会创建单例对象。它不像饿汉模式那样在程序启动时就立即创建实例,而是在需要的时候才进行实例化。

优点:节省了内存,线程安全
缺点:性能低

三种创建方式

  • 第一种 不加锁
    //懒汉单例public lass Demo2{private static Demo2 instance;public Demo2(){}public static Demo2 getInstance(){if (instance == null){instance = new Demo2();}return instance;}}

无法保证单例

  • 第二种 增加 synchronized 锁
    //懒汉加锁public class Demo3{private static Demo3 instance;public Demo3(){}public synchronized static Demo3 getInstance(){if (instance == null){instance = new Demo3();}return instance;}}

可以保证单例 但性能较低 所有的线程全都被阻塞到方法外部排队处理

  • 第三种 双重校验单例
   //懒汉双重校验public class Demo4{private static Demo4 instance;public Demo4(){}public  static Demo4 getInstance(){if (instance == null){synchronized(Demo4.class){if (instance == null){instance = new Demo4();}}}return instance;}}

只锁创建方法提高性能,可以保证单例 性能还高 可以避免不必要的加锁
优点: 性能高了,线程安全了
缺点:可读性难度加大,不够优雅

第三种 枚举单例

在这种实现方式中,既可以避免多线程同步问题,还可以防止通过反射和反序列化来重新创建新的对象。
Java虚拟机会保证枚举对象的唯一性,因此每一个枚举类型和定义的枚举变量在JVM中都是唯一的。

public enum Demo5 {INSTANCE;private Object data;public Object getData() {return data;}public void setData(Object data) {this.data = data;}
}

INSTANCE 是 Demo5 枚举类的唯一实例。当程序运行时,Demo5.INSTANCE 就是该枚举类的唯一存在,也就是单例实例。

第四种 Spring中的单例模式实现 也可以称为 容器化单例

Spring 源码中的 DefaultSingletonBeanRegistry 类 getSingleton 方法

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lock.Object singletonObject = this.singletonObjects.get(beanName); // 尝试从 singletonObjects 缓存中直接获取已存在的单例对象。这个步骤不加锁,是为了提高性能。if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 如果未找到单例对象,并且该单例对象正在创建中,进入下一个判断。singletonObject = this.earlySingletonObjects.get(beanName); // 尝试从 earlySingletonObjects 缓存中获取提前引用的对象。if (singletonObject == null && allowEarlyReference) {// 如果 still 没有找到对象,并且允许提前引用时,尝试获取对象。if (!this.singletonLock.tryLock()) {// 如果无法获取锁,则避免在创建过程中返回提前引用,防止线程不安全的情况。return null;}try {// 在获取锁后,确保完整的单例创建过程。singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {// 如果单例对象还是没找到,进一步检查 earlySingletonObjects。singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {// 如果 earlySingletonObjects 中也没有找到,则需要从 singletonFactories 获取对象。ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {// 从 singletonFactories 中获取单例对象工厂,并调用 getObject() 创建对象。singletonObject = singletonFactory.getObject();// 获取对象后,检查该对象是否已添加或移除。if (this.singletonFactories.remove(beanName) != null) {// 如果工厂从 singletonFactories 中移除,说明创建了对象,放入 earlySingletonObjects 中。this.earlySingletonObjects.put(beanName, singletonObject);}else {// 如果对象被移除,说明对象已存在于 singletonObjects 中。singletonObject = this.singletonObjects.get(beanName);}}}}}finally {this.singletonLock.unlock(); // 无论如何释放锁,保证线程安全。}}}return singletonObject; // 返回找到的单例对象,如果找不到,则返回 null。
}

这里涉及到三个单例容器:

  • singletonObjects:
    这是最终存放已创建单例对象的缓存。正常情况下,当一个单例对象创建完成后,它会被放入这个缓存中,供后续的使用和访问。
    只有当对象完全创建完成且没有依赖其他对象时,它才会进入这个缓存。
  • earlySingletonObjects:
    当一个单例对象正在被创建时,可能有其他的 bean 依赖于它。为了防止这种依赖造成死锁或递归调用,Spring 会在对象创建的过程中将当前已经部分初始化的对象放到这个缓存中,供其他 bean 在创建过程中访问。
    这就是所谓的 “提前曝光”,指的是在对象完全初始化之前,Spring 就让它能被其他依赖的 bean 使用。
  • singletonFactories:
    这个缓存中存放的是 ObjectFactory 对象,也就是单例对象的工厂。它们并不直接存储单例实例,而是存储生成单例实例的工厂。只有在没有找到单例对象(在前两个缓存中都找不到时),Spring 才会通过这些工厂来创建对象。
    这个缓存确保了在单例对象工厂可以提供实例之前,不会因为某个对象的引用而导致创建死锁。

单例的获取顺序是singletonObjects -> earlySingletonObjects -> singletonFactories 这样的三级缓存
singletonObjects 指单例对象的缓存,singletonFactories 指单例对象工厂的缓存,earlySingletonObjects 指提前曝光的单例对象的缓存。
以上三个构成了三级缓存,Spring 就用这三级缓存巧妙的解决了循环依赖问题。

这里引发一个思考: 为什么要使用三级缓存才能解决循环依赖呢?这里转载一篇博客

原文链接:https://blog.csdn.net/qq_33204709/article/details/130423123

在这里插入图片描述

如果只使用一级缓存,我们可以根据上面的例子看到,类A和类B都不存在,根本没有初始化完成的对象可以存放到一级缓存中,所以循环依赖没有修复(死循环)

如果想打破上面循环依赖的死循环,就需要一个另一个缓存来将已经实例化但是没有完成依赖注入的对象给缓存起来这就是二级缓存。
在这里插入图片描述
然后再配合一级缓存,我们将创建好的单例对象存放到单例池中,同时清空二级缓存中对应的原始对象(半成品实例)
在这里插入图片描述
看到这里,我们就会有疑问,这不是一级缓存 + 二级缓存已经解决了循环依赖的问题了吗?为什么还需要三级缓存?

假如类A被增强了,那么我们需要注入到Bean容器中的就是A的代理对象,那么经过上面一整套流程下来,存放到一级缓存中的并不会是代理对象A,而是对象A。

为了将对应的代理对象A的实例也注入到容器中,这里我们就需要使用三级缓存了。

首先,我们在实例化A之后,将A中用于创建代理对象A的工厂对象 A-ObjectFactory,和B中用于创建对象B的工厂对象 B-ObjectFactor 放到三级缓存中。

并使用A的工厂对象 A-ObjectFactory 作为A的实例注入到A中。
在这里插入图片描述
然后,我们通过A的ObjectFactory对象创建A的代理对象(半成品/原始对象),然后将A的代理对象注入给B,就可以将B创建成功。
在这里插入图片描述
最后,我们将创建好的B放入单例池中,然后将B注入给A,这样我们就可以最终将A创建成功,然后将创建好的A再放入单例池中。

在这里插入图片描述
这样我们就成功使用三级缓存来解决了创建对象时的循环依赖的问题。

三级缓存只是解决了构造函数之后的循环依赖问题,那么构造函数的循环依赖问题怎么解决呢?
在这里插入图片描述
Spring 给我们提供了一个 @Lazy 注解,也叫懒加载,或延迟加载。被这个注解修饰的对象,只有在使用的时候才会创建实例,那时单例池中的其他对象都已经创建好了,便解决了循环依赖的问题。

第五种 特殊单例 线程单例

顾名思义 保证在所有线程内的单例
常见使用场景 日志框架 确保每个线程内都有一个单例日志实例 保证日志记录和输出的唯一性
在线程内最常使用的 TheadLocal 可以保证线程之间的变量隔离 基于他来实现线程单例

public class ThreadLocalSingleton {// 通过 ThreadLocal 的初始化方法 withInitial 初始化对象实例 保证线程唯一private static final ThreadLocal<ThreadLocalSingleton> threadLocaLInstance =ThreadLocal.withInitial(() -> new ThreadLocalSingleton());private ThreadLocalSingleton(){}public static ThreadLocalSingleton getInstance(){return threadLocaLInstance.get();}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/69735.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenCV 相机标定流程指南

OpenCV 相机标定流程指南 前置准备标定流程结果输出与验证建议源代码 OpenCV 相机标定流程指南 https://docs.opencv.org/4.x/dc/dbb/tutorial_py_calibration.html https://learnopencv.com/camera-calibration-using-opencv/ 前置准备 制作标定板&#xff1a;生成高精度棋…

没有服务器和显卡电脑如何本地化使用deepseek|如何通过API使用满血版deepseek

目录 一、前言二、使用siliconflow硅基流动 API密钥1、注册硅基流动2、创建API密钥3、下载AI客户端4、使用API密钥5、效果演示 三、使用deepseek官方API密钥1、创建API密钥2、使用API密钥3、效果演示 四、总结 一、前言 上篇文章我介绍了如何通过云服务器或者显卡电脑来本地化…

python+unity落地方案实现AI 换脸融合

先上效果再说技术结论&#xff0c;使用的是自行搭建的AI人脸融合库&#xff0c;可以离线不受限制无限次生成&#xff0c;有需要的可以后台私信python ai换脸融合。 TODO 未来的方向&#xff1a;3D人脸融合和AI数据训练 这个技术使用的是openvcinsighface&#xff0c;openvc…

windows + visual studio 2019 使用cmake 编译构建静、动态库并调用详解

环境 windows visual studio 2019 visual studio 2019创建cmake工程 1. 静态库.lib 1.1 静态库编译生成 以下是我创建的cmake工程文件结构&#xff0c;只关注高亮文件夹部分 libout 存放编译生成的.lib文件libsrc 存放编译用的源代码和头文件CMakeLists.txt 此次编译CMak…

【前端】几种常见的跨域解决方案代理的概念

几种常见的跨域解决方案&代理的概念 一、常见的跨域解决方案1. 服务端配置CORS&#xff08;Cross-Origin Resource Sharing&#xff09;&#xff1a;2. Nginx代理3. Vue CLI配置代理&#xff1a;4 .uni-app在manifest.json中配置代理来解决&#xff1a;5. 使用WebSocket通讯…

Git 分布式版本控制工具使用教程

1.关于Git 1.1 什么是Git Git是一款免费、开源的分布式版本控制工具&#xff0c;由Linux创始人Linus Torvalds于2005年开发。它被设计用来处理从很小到非常大的项目&#xff0c;速度和效率都非常高。Git允许多个开发者几乎同时处理同一个项目而不会互相干扰&#xff0c;并且在…

基于java手机销售网站设计和实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

GitHub Pages + Jekyll 博客搭建指南(静态网站搭建)

目录 &#x1f680; 静态网站及其生成工具指南&#x1f30d; 什么是静态网站&#xff1f;&#x1f4cc; 静态网站的优势⚖️ 静态网站 VS 动态网站 &#x1f680; 常见的静态网站生成器对比&#x1f6e0;️ 使用 GitHub Pages Jekyll 搭建个人博客&#x1f4cc; 1. 创建 GitHu…

1.【线性代数】——方程组的几何解释

一 方程组的几何解释 概述举例举例一1. matrix2.row picture3.column picture 概述 三种表示方法 matrixrow picturecolumn picture 举例 举例一 { 2 x − y 0 − x 2 y 3 \begin{cases} 2x - y 0 \\ -x 2y 3 \end{cases} {2x−y0−x2y3​ 1. matrix [ 2 − 1 − 1 …

ZZNUOJ(C/C++)基础练习1091——1100(详解版)⭐

目录 1091 : 童年生活二三事&#xff08;多实例测试&#xff09; C C 1092 : 素数表(函数专题&#xff09; C C 1093 : 验证哥德巴赫猜想&#xff08;函数专题&#xff09; C C 1094 : 统计元音&#xff08;函数专题&#xff09; C C 1095 : 时间间隔&#xff08;多…

innovus如何分步长func和dft时钟

在Innovus工具中&#xff0c;分步处理功能时钟&#xff08;func clock&#xff09;和DFT时钟&#xff08;如扫描测试时钟&#xff09;需要结合设计模式&#xff08;Function Mode和DFT Mode&#xff09;进行约束定义、时钟树综合&#xff08;CTS&#xff09;和时序分析。跟随分…

java高级知识之集合

前言 集合是java开发中的重点内容&#xff0c;需要掌握的东西很多&#xff0c;面试中可问的东西很多&#xff0c;无论是深度还是广度。集合框架中Collection对应的实现类如下所示&#xff0c;这些都是要完全掌握&#xff0c;一个可以分为三大类List集合、Set‘集合以及Map集合…

51c自动驾驶~合集49

我自己的原文哦~ https://blog.51cto.com/whaosoft/13164876 #Ultra-AV 轨迹预测新基准&#xff01;清华开源&#xff1a;统一自动驾驶纵向轨迹数据集 自动驾驶车辆在交通运输领域展现出巨大潜力&#xff0c;而理解其纵向驾驶行为是实现安全高效自动驾驶的关键。现有的开…

Unity-Mirror网络框架-从入门到精通之MultipleMatches示例

文章目录 前言MultipleMatchesLobbyViewRoomViewMatchGUIPlayerGUI总结前言 在现代游戏开发中,网络功能日益成为提升游戏体验的关键组成部分。本系列文章将为读者提供对Mirror网络框架的深入了解,涵盖从基础到高级的多个主题。Mirror是一个用于Unity的开源网络框架,专为多人…

VMware Workstation创建虚拟机

目录 创建新的虚拟机 虚拟机快照功能 虚拟机添加空间 其他注意事项 创建新的虚拟机 打开VMware Workstation&#xff1a;启动软件后&#xff0c;点击“创建新的虚拟机”。 选择安装方式&#xff1a; 典型安装&#xff1a;适合大多数用户&#xff0c;会自动完成大部分配置…

DeepSeek AI R1推理大模型API集成文档

DeepSeek AI R1推理大模型API集成文档 引言 随着自然语言处理技术的飞速发展&#xff0c;大语言模型在各行各业的应用日益广泛。DeepSeek R1作为一款高性能、开源的大语言模型&#xff0c;凭借其强大的文本生成能力、高效的推理性能和灵活的接口设计&#xff0c;吸引了大量开发…

活泼瘤胃球菌(Ruminococcus gnavus)——多种疾病风险的潜在标志物

​ 前几日&#xff0c;南方医科大学深圳医院院长周宏伟教授团队在国际顶尖医学期刊《Nature Medicine》上发表了一项重要研究。首次揭示一种名为活泼瘤胃球菌(Ruminococcus gnavus)的细菌产生的物质——苯乙胺&#xff0c;在肝性脑病发生中的关键作用。 ​ 同时谷禾的人群检测数…

8.flask+websocket

http是短连接&#xff0c;无状态的。 websocket是长连接&#xff0c;有状态的。 flask中使用websocket from flask import Flask, request import asyncio import json import time import websockets from threading import Thread from urllib.parse import urlparse, pars…

qiime2:安装与使用

试一下docker安装 docker pull quay.io/qiime2/amplicon:2024.10 docker images docker run -v {挂载的目录}:/data quay.io/qiime2/amplicon:2024.10 qiime -h使用 import.txt docker run -v ~/diarrhoea/MJ/qingzhu:/data quay.io/qiime2/amplicon:2024.10 qiime tools imp…

技术实战|ELF 2学习板本地部署DeepSeek-R1大模型的完整指南(一)

DeepSeek作为国产AI大数据模型的代表&#xff0c;凭借其卓越的推理能力和高效的文本生成技术&#xff0c;在全球人工智能领域引发广泛关注。DeepSeek-R1作为该系列最新迭代版本&#xff0c;实现了长文本处理效能跃迁、多模态扩展规划、嵌入式适配等技术维度的突破。 RK3588作为…