面试:Spring中单例模式用的是哪种?

你好,我是田哥

需要简历优化、模拟面试、面试辅导、技术辅导......请联系我。10年码农24小时在线为你服务。

面试中被问到设计模式的概率还是蛮高的,尤其是问:你在项目中用过设计模式吗?

面对这个问题,我也在做模拟面试时问过很多人,大部分都会回答Spring中的单例模式。但是只要追问:单例模式有很多种写法,那Spring中用的是哪一种呢?于是很多朋友一脸懵。

单例模式

单例模式是一种常用的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。

在实现单例模式时,常见的几种写法包括:

  1. 饿汉式(Eager Initialization):

  • 优点:线程安全,实现简单,不需要考虑多线程同步问题。

  • 缺点:在类加载时就创建实例,可能会造成资源浪费。

懒汉式(Lazy Initialization):

  • 优点:延迟加载,只有在第一次使用时才会创建实例。

  • 缺点:线程不安全,需要考虑多线程同步问题。

双重检查锁定(Double-Checked Locking):

  • 优点:延迟加载,线程安全。

  • 缺点:实现较为复杂。

静态内部类(Static Inner Class):

  • 优点:延迟加载,线程安全,实现简单。

  • 缺点:无法传递参数给构造函数。

枚举(Enum):

  • 优点:线程安全,实现简单,可以防止反射和序列化攻击。

  • 缺点:无法延迟加载。

每种写法都有其优点和缺点,选择适合的写法取决于具体的需求和场景。

每种模式的写法参考这篇文章:单例模式,被问7个问题,难!

到底用哪些模式?

如果对线程安全要求较高,可以选择饿汉式或双重检查锁定;如果对延迟加载要求较高,可以选择懒汉式或静态内部类;如果需要防止反射和序列化攻击,可以选择枚举实现单例模式。

spring 单例模式

Spring框架提供了一种单例模式的实现方式,即通过IoC容器管理Bean的生命周期来实现单例模式。

在Spring中,通过在配置文件或者注解中声明Bean的作用域为singleton,就可以将该Bean定义为单例模式。当容器初始化时,会创建该Bean的一个实例,并将其放入容器中。之后,每次请求该Bean时,都会返回同一个实例。

Spring的单例模式实现原理主要有以下几个步骤:

  1. 容器初始化:当Spring容器启动时,会读取配置文件或者注解,解析Bean的定义信息,并创建Bean的实例。

  2. 创建单例Bean:当容器创建Bean的实例时,会根据Bean的作用域来判断是否需要创建单例Bean。如果Bean的作用域为singleton,则容器会创建一个单例Bean的实例,并将其放入容器中。

  3. 容器管理单例Bean:容器会将创建的单例Bean实例放入一个缓存中,以便后续的请求可以直接返回该实例。

  4. 返回单例Bean:每次请求该单例Bean时,容器会直接从缓存中获取该实例,并返回给调用方。

需要注意的是,Spring的单例模式是基于容器的,即容器负责管理Bean的生命周期和实例化过程。因此,开发人员无需手动管理单例对象的创建和销毁,只需要通过容器来获取单例Bean的实例即可。

下面是一个使用Spring注解方式实现单例模式的示例:

@Component
@Scope("singleton")
public class SingletonBean {// 单例Bean的属性和方法
}

在上述示例中,通过@Component注解将该类声明为一个Bean,并使用@Scope("singleton")注解将其作用域定义为singleton,从而实现了单例模式。

Spring Bean单例模式的设计

Spring Bean采用了双重校验锁以及ConcurrentHashMap作为容器实现了单例设计,并且通过三级缓存解决循环依赖的问题。我们来看下Spring Bean的创建方法,在AbstractBeanFactory类中。

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {String beanName = transformedBeanName(name);Object beanInstance;//先判断容器中是否存在Object sharedInstance = getSingleton(beanName);if (sharedInstance != null && args == null) {//...省略beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);}else {//...省略 判断BeanDefinition 是否存在...try {if (requiredType != null) {beanCreation.tag("beanType", requiredType::toString);}RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);checkMergedBeanDefinition(mbd, beanName, args);//...省略}//进行Bean的实例化if (mbd.isSingleton()) {//调用DefaultSingletonBeanRegistry的getSingleton方法,使用lambda表达式sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {}});}//...省略}

可以看到在创建Bean之前会先去判断容器中是否存在Bean对象,存在的话直接获取,代码如下:

//一级缓存private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);//二级缓存private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);//三级缓存private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);//获取单例Bean 一级缓存 -> 二级缓存 -> 三级缓存protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) { //同步锁,解决Bean存在于三级缓存HashMap中的线程安全问题singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}}return singletonObject;
}

当三级缓存中都不存在相应的Bean对象时,则进行Bean对象的创建,调用DefaultSingletonBeanRegistry的getSingleton方法:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(beanName, "Bean name must not be null");synchronized (this.singletonObjects) { //同步锁Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {if (this.singletonsCurrentlyInDestruction) {//...省略}//...省略boolean newSingleton = false;boolean recordSuppressedExceptions = (this.suppressedExceptions == null);if (recordSuppressedExceptions) {//...省略}try {//singletonFactory为函数式接口,由上面可知此方法会去创建Bean 会调用createBean方法singletonObject = singletonFactory.getObject();newSingleton = true;}catch (IllegalStateException ex) {//...省略}catch (BeanCreationException ex) {//...省略}finally {//...省略}if (newSingleton) {//添加单例Bean进入容器中addSingleton(beanName, singletonObject);}}return singletonObject;}
}

addSingleton()方法:

protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) { //同步锁this.singletonObjects.put(beanName, singletonObject); //添加对象进入一级缓存this.singletonFactories.remove(beanName); //移除三级缓存对象this.earlySingletonObjects.remove(beanName); //移除二级缓存对象this.registeredSingletons.add(beanName);}
}

可以看出在Spring中是通过类似双重校验锁方式并配合ConcurrentHashMap这个线程安全的HashMap,来完成Bean的单例创建,使得默认生成的Bean在容器中有且仅有一个,也保证了在创建过程中内存有且仅有一个对象

在线刷题小程序

再聊几句

文章前面提到面试官问你在项目中有没有用过什么设计模式,Spring中的单例模式是人家实现Bean单例而使用的单例模式,面试官更多的是想问你在项目中某个业务场景中用到过什么设计模式。

所以,在面试之前,建议你想想之前做过的项目中用过什么什么设计模式。

推荐准备:

  • 单例模式

  • 策略模式

  • 模板方法

  • 装饰器模式

这四种设计模式相对来说,在项目中运用场景比较多,通用性相对比较强。

好了,今天就分享这么多。

充电桩项目中,就用到策略模式+工厂模式模板方法模式

78c3515e7c729cf6981cd904fbe050de.png

注意充电桩项目代码已开源了,后台回复项目实战即可获取。

面试辅导:全程面试辅导,保驾护航!

个人技术博客可刷题:https://woaijava.cc/

回复77 ,获取《面试小抄2.0版》

回复电子书,获取后端必读的200本电子书籍

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

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

相关文章

蓝桥杯每日一题2023.9.22

4960. 子串简写 - AcWing题库 题目描述 题目分析 原本为纯暴力但是发现会超时&#xff0c;可以加入前缀和&#xff0c;从前往后先记录一下每个位置c1出现的次数 再从前往后扫一遍&#xff0c;如果遇到c2就将答案加上此位置前的所有c1的个数&#xff08;直接加上此位置的前缀…

React 全栈体系(十六)

第八章 React 扩展 五、Context 1. 代码 /* index.jsx */ import React, { Component } from react import ./index.css//创建Context对象 const MyContext React.createContext() const {Provider,Consumer} MyContext export default class A extends Component {state …

构建卓越语言模型应用的利器:LangChain | 开源日报 No.39

langchain-ai/langchain Stars: 61.3k License: MIT LangChain 是一个用于通过组合性构建 LLMs 应用程序的库。 LLMs 和 Prompts&#xff1a;包括 prompt 管理、prompt 优化、所有 LLM 的通用接口以及与 LLMs 一起使用的常见工具。Chains&#xff1a;超越单个 LLM 调用&…

Logic Pro X10.7.9(mac乐曲制作软件)

Logic Pro X是由苹果公司开发的一款专业音频制作软件&#xff0c;主要用于音乐制作、录音、混音和母带处理等方面。以下是Logic Pro X的特点&#xff1a; 强大的音频编辑功能&#xff1a;Logic Pro X提供了丰富的音频编辑工具&#xff0c;包括波形编辑器、音频自动化、时间拉伸…

W5100S_EVB_PICO快速入门之MQTT篇(十二)

目录 1. 前言 2. MQTT介绍 2.1 什么是mqtt&#xff1f; 2.2 特点 2.3 应用场景 2.4 MQTT协议实现方式 3. 硬件及接线方式 3.1 硬件准备 3.2 硬件介绍 3.3 接线图 4. 测试 4.1 MQTT测试流程图 4.2 相关代码 4.3 测试现象 5. 相关链接&#xff1a; 1. 前言 随着物…

ArduPilot开源飞控之GCS显示DPS310异常问题

ArduPilot开源飞控之GCS显示DPS310异常问题 1. 源由2. 现象3. 分析3.1 Mission Planner3.2 Ardupilot3.3 AP_Baro分析3.4 AP_Baro定位 4. 修复5. 效果6. 参考资料7. 补充7.1 Ardupilot提交PR注意事项7.2 修复主要使用到的命令 1. 源由 2020年Ardupilot官网论坛就有开始讨论DPS…

邮件功能-python中的SMTP协议邮件发送

文章目录 一、SMTP协议邮件准备二、smtplib模块1.使用smtplib封装一个邮件类2.发送邮件 补充 一、SMTP协议邮件准备 需要一个smtp服务器 二、smtplib模块 smtplib模块是python自带的模块 1.使用smtplib封装一个邮件类 import smtplib import logging # 加入日志&#xff…

ORACLE 内存结构之系统全局区(SGA)

每个 Oracle 数据库实例都会在内存中分配一个很大的内存结构&#xff0c; 称为系统全局区(System Global Area), 这是一个大型的共享内存结构,每个Oracle进程都会访问它。 在Linux/Unix操作系统上,SGA是一个物理实体&#xff0c;使用操作系统命令能“看到它”。 它被操作系…

一维卷积神经网络

假设输入数据维度为8&#xff0c;filter维度为5&#xff1b; 不加padding时&#xff0c;输出维度为4&#xff0c;如果filter的数量为16&#xff0c;那么输出数据的shape就是4*16. 一维卷积不代表卷积核只有一维&#xff0c;也不代表被卷积的feature也是一维。一维的意思是说卷…

RIP路由

目录 RIP路由 1、什么是RIP路由 2、RIP的工作原理是什么 3、RIP v1 和 RIP v2的区别 4、RIP的常用场景 5、RIP的通信流程 6、RIP的优缺点 优点&#xff1a; 缺点&#xff1a; 7、扩展部分 1.RIP路由的作用与应用场景 2.与其他路由协议的区别 3.RIP路由协议的工作原…

OpenCV显示10bit Raw数据

参考&#xff1a;10 12 14bit图像存储格式&#xff0c;利用Opencv显示10bit Raw数据,并根据鼠标的移动显示对应位置的灰度值。其他bit位数的Raw数据方法类似。 代码实现&#xff1a; #include<opencv2/opencv.hpp> #include<iostream> #include<opencv/highgu…

2023年毫米波行业研究报告

第一章 行业概况 1.1 定义 毫米波是一种电磁波&#xff0c;其波长范围在1毫米至10毫米之间&#xff0c;频率介于30GHz至300GHz。与sub-6G (6GHz以下频段&#xff09;的5G系统相比&#xff0c;5G毫米波通信在带宽、时延和灵活弹性空口配置方面具有明显优势。这使其能够有效地满…

【C语言练习】DOS黑框框通讯录(使用结构体、动态内存管理联系人信息,函数指针等)

文章目录 1. contacts.h 头文件、函数/常量/结构体声明2. test.c 主界面菜单打印、菜单功能选项选择3. contacts.c 函数实现4. 使用结构体、动态内存&#xff0c;函数指针实现时的注意点5. 运行演示 1. contacts.h 头文件、函数/常量/结构体声明 #pragma once#include <std…

探索公共厕所的数字化治理,智慧公厕完善公共厕所智能化的治理体系

随着城市化进程的不断发展&#xff0c;公共厕所治理成为一个不容忽视的问题。如何通过数字化手段来提升公共厕所管理水平&#xff0c;成为了一个备受关注的话题。本文将以智慧公厕领先厂家广州中期科技有限公司&#xff0c;大量精品案例项目实景实图&#xff0c;探讨公共厕所数…

使用transformers进行端到端的目标检测

目录 目标检测的旧方法 使用transformers进行端到端的目标检测 抛去了目标检测旧的方法 网络架构 Transformer encoder Transformers and Parallel Decoding 注意力起到的作用 使用Hungarian algorithm算法完成匹配 在使用transformers的端到端目标检测中&#xff0c;匈…

【开发篇】七、RedisTemplate与StringRedisTemplate + Jedis与Lettcus

文章目录 1、RedisTemplate详解2、常用方法3、关于IDEA的报黄4、RedisTemplate和StringRedisTemplate的区别5、如何通用RedisTemplate和StringRedisTemplate6、Jedis7、Jedis的连接池8、封装Jedis工具类8、RedisTemplate底层实现技术切换 1、RedisTemplate详解 RedisTemplate是…

spark Structured报错解决

报错&#xff0c;不想看原因的直接去解决方案试试 Exception in thread "main" java.lang.IllegalArgumentException: Pathname /C:/Users/Administrator/AppData/Local/Temp/1/temporary-611514af-8dc5-4b20-9237-e5f2d21fdf88/metadata from hdfs://master:8020/C…

C语言的stdio.h的介绍

C语言的stdio.h的介绍 C语言的stdio.h的介绍 C语言的stdio.h的介绍C语言stdio.h的介绍 C语言stdio.h的介绍 这个含义是导入标准输入输出库 包含头文件.h&#xff0c;std标准库&#xff0c;io是input output输入输出库 <>代表系统库&#xff0c;自定义的话用""…

Ingress Controller

什么是 Ingress Controller &#xff1f; 在云原生生态中&#xff0c;通常来讲&#xff0c;入口控制器( Ingress Controller )是 Kubernetes 中的一个关键组件&#xff0c;用于管理入口资源对象。 Ingress 资源对象用于定义来自外网的 HTTP 和 HTTPS 规则&#xff0c;以控制进…

解决typescript报错:不能将类型xxx分配给类型xxx

现象&#xff1a; 这种情况是因为组件传参时&#xff1a; 等号左右两边的数据类型不能严格匹配一致造成的 等号左边data, 查看一下被传参的子组件ProductList的内部data属性: 可以看到data的类型是 &#xff1a; Product[] 而右边的shoppingCartItems来自于&#xff1a; redu…