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

狮子大佬原文
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();
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从技术体系到实践案例:浪潮信息解码金融算力演进路径

作为金融科技领域的重要参与者&#xff0c;浪潮信息作为核心参编单位&#xff0c;联合中国金电、工商银行等33家机构共同完成《中国金融科技发展报告&#xff08;2024&#xff09;》&#xff08;以下简称蓝皮书&#xff09;编撰。浪潮信息凭借在数字基础设施领域的技术积累&…

题海拾贝:【高精度】减法

Hello大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion&#xff0c;开始今天的编程之路&#xff01; 我的博客&#xff1a;<但凡. 我的专栏&#xff1a;《编程之路》、《数据结构与算法之美》、《题海拾贝》 欢迎点赞&#xff0c;关注&#xff01; 1、题…

知识库升级新思路:用生成式AI打造智能知识助手

在当今信息爆炸的时代&#xff0c;企业和组织面临着海量数据的处理和管理挑战。知识库管理系统&#xff08;Knowledge Base Management System, KBMS&#xff09;作为一种有效的信息管理工具&#xff0c;帮助企业存储、组织和检索知识。然而&#xff0c;传统的知识库系统往往依…

设计模式-生产者消费者模型

阻塞队列&#xff1a; 在介绍生产消费者模型之前&#xff0c;我们先认识一下阻塞队列。 阻塞队列是一种支持阻塞操作的队列&#xff0c;常用于生产者消费者模型&#xff0c;它提供了线程安全的队列操作&#xff0c;并且在队列为空或满时&#xff0c;能够阻塞等待&#xff0c;…

1Panel应用推荐:WordPress开源博客软件和内容管理系统

1Panel&#xff08;github.com/1Panel-dev/1Panel&#xff09;是一款现代化、开源的Linux服务器运维管理面板&#xff0c;它致力于通过开源的方式&#xff0c;帮助用户简化建站与运维管理流程。为了方便广大用户快捷安装部署相关软件应用&#xff0c;1Panel特别开通应用商店&am…

计算机毕业设计Tensorflow+LSTM空气质量监测及预测系统 天气预测系统 Spark Hadoop 深度学习 机器学习 人工智能

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

语言月赛 202308【小粉兔做麻辣兔头】题解(AC)

》》》点我查看「视频」详解》》》 [语言月赛 202308] 小粉兔做麻辣兔头 题目描述 粉兔喜欢吃麻辣兔头&#xff0c;麻辣兔头的辣度分为若干级&#xff0c;用数字表示&#xff0c;数字越大&#xff0c;兔头越辣。为了庆祝粉兔专题赛 #1 的顺利举行&#xff0c;粉兔要做一些麻…

激活函数篇 02 —— 双曲正切函数tanh

本篇文章收录于专栏【机器学习】 以下是激活函数系列的相关的所有内容: 一文搞懂激活函数在神经网络中的关键作用 逻辑回归&#xff1a;Sigmoid函数在分类问题中的应用 tanh ⁡ ( x ) e x − e − x e x e − x \tanh(x)\frac{e^x - e^{-x}}{e^x e^{-x}} tanh(x)exe−xex…

STM32G0B1 ADC DMA normal

目标 ADC 5个通道&#xff0c;希望每1秒采集一遍&#xff1b; CUBEMX 配置 添加代码 #define ADC1_CHANNEL_CNT 5 //采样通道数 #define ADC1_CHANNEL_FRE 3 //单个通道采样次数&#xff0c;用来取平均值 uint16_t adc1_val_buf[ADC1_CHANNEL_CNT*ADC1_CHANNEL_FRE]; //传递…

【数据结构】链表应用1

链表应用 面试题 02.02.返回倒数第k个节点题目描述思路解题过程复杂度 查找相同后缀题目描述解题思路完整代码&#xff1a; 删除绝对值相等的节点题目描述解题思路代码 面试题 02.02.返回倒数第k个节点 题目描述 实现一种算法&#xff0c;找出单向链表中倒数第 k 个节点。返回…

【JVM详解一】类加载过程与内存区域划分

一、简介 1.1 概述 JVM是Java Virtual Machine&#xff08;Java虚拟机&#xff09;的缩写&#xff0c;是通过在实际的计算机上仿真模拟各种计算机功能来实现的。由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域等组成。JVM屏蔽了与操作系统平台相关…

基于STM32设计的仓库环境监测与预警系统

目录 项目开发背景设计实现的功能项目硬件模块组成设计思路系统功能总结使用的模块的技术详情介绍总结 1. 项目开发背景 随着工业化和现代化的进程&#xff0c;尤其是在制造业、食品业、医药业等行业&#xff0c;仓库环境的监控和管理成为了至关重要的一环。尤其是在存储易腐…

“可通过HTTP获取远端WWW服务信息”漏洞修复

环境说明&#xff1a;①操作系统&#xff1a;windows server&#xff1b;②nginx&#xff1a;1.27.1。 1.漏洞说明 “可通过HTTP获取远端WWW服务信息”。 修复前&#xff0c;在“响应标头”能看到Server信息&#xff0c;如下图所示&#xff1a; 修复后&#xff0c;“响应标头…

创建一个javaWeb Project

文章目录 前言一、eclipse创建web工程二、web.xmlservlet.xml< mvc:annotation-driven/ > Spring MVC 驱动< context:component - scan >&#xff1a;扫描< bean > ... < /bean >< import > config/beans.xml beans.xmlmybatis.xml 前言 javaWe…

aspectFill(填充目标区域的同时保持图像的原有宽高比 (aspect ratio)图像不会被拉伸或压缩变形

“aspectFill” 是一个常用于图像和视频处理的术语&#xff0c;尤其是在用户界面 (UI) 设计和图形编程领域。它描述的是一种图像缩放或调整大小的方式&#xff0c;旨在填充目标区域的同时保持图像的原有宽高比 (aspect ratio)。 更详细的解释: Aspect Ratio (宽高比): 指的是图…

界址点成果表批量生成(新增.docx格式)-附工具下载链接

界址点编号工具20250208更新&#xff08;成果表新增.docx格式&#xff09;。 【工具简介】工具可根据面图层&#xff0c;西北角顺时针批量生成界址点&#xff0c;可以设置角度、距离参数&#xff0c;来减少生成界址点的数量&#xff08;不用全部节点生成界址点&#xff09;。生…

《redis缓存淘汰机制》

【redis缓存淘汰机制导读】redis作为一款内存型数据库&#xff0c;其设计的初衷就是为了给广大业务层提供高效的数据读、写能力&#xff0c;因为访问内存的速度肯定是要比直接访问磁盘的速度快几个数量级&#xff0c;假设业务方所有数据读、写请求全部都转发到后台的数据库&…

AWK系统学习指南:从文本处理到数据分析的终极武器 介绍

目录 一、AWK核心设计哲学解析 1.1 记录与字段的原子模型 1.2 模式-动作范式 二、AWK编程语言深度解析 2.1 控制结构 说明&#xff1a; 2.2 关联数组 代码说明&#xff1a; 示例输入和输出&#xff1a; 注意事项&#xff1a; 2.3 内置函数库 三、高级应用技巧 3.1…

深入解析AI技术原理

序言 在当今数字化时代,人工智能(AI)已经成为科技领域最炙手可热的话题之一。从智能家居到自动驾驶汽车,从医疗诊断到金融风险预测,AI的应用无处不在。然而,对于许多人来说,AI背后的技术原理仍然充满了神秘色彩。本文将深入探讨AI的核心技术原理,从基础理论到前…

计算机组成原理(3)

计算机组成原理&#xff08;3&#xff09; 存储器层次结构存储器概述存储器分类存储器性能指标 半导体随机存储SRAM和DRAM 存储器层次结构 主存-辅存&#xff1a;实现了虚拟存储系统&#xff0c;解决了主存容量不足的问题&#xff1b; Cache-主存&#xff1a;解决了主存于CPU速…