面试官听了我说的单例设计模式,让我出门右转

简单说两句

✨ 正在努力的小叮当~
💖 超级爱分享,分享各种有趣干货!
👩‍💻 提供:模拟面试 | 简历诊断 | 独家简历模板
🌈 感谢关注,关注了你就是我的超级粉丝啦!
🔒 以下内容仅对你可见~

作者:小叮当撩代码CSDN后端领域新星创作者 |阿里云专家博主

CSDN个人主页:小叮当撩代码

🔎GZH哆啦A梦撩代码

🎉欢迎关注🔎点赞👍收藏⭐️留言📝

面试官听了我说的单例设计模式,让我出门右转

文章目录

    • 面试官听了我说的单例设计模式,让我出门右转
      • 🤣前言
      • 📖概念
      • 📋 应用场景
      • 🥲懒汉式-线程不安全
      • 🙊懒汉式-线程安全
      • 💓饿汉式
      • 💖枚举实现
      • 🍇容器实现

image-20240720213816054

🤣前言

我走进面试室,面试官一脸严肃地递给我一张纸和一支笔,说:“请手写一个单例模式。

”我微微一笑,深吸一口气,然后开始在纸上画起了小鸡啄米图,边画边说:“这是单例鸡,它只能有一个,多了就乱套了。”面试官愣住了,然后你接着解释:“单例模式嘛,就像这个小鸡,无论世界多大,它都是独一无二的。” 🤣🤣🤣

好勒,开个玩笑,我们下面开始正式开始单例模式手搓环节哈🤣

📖概念

单例设计模式是一种确保一个类在整个应用程序中只有一个实例存在的设计模式。这意味着无论在何处访问该类,得到的都是同一个实例对象。它通过限制类的构造函数的访问权限,以及提供一个静态方法来获取唯一的实例,实现了实例的唯一性控制。

📋 应用场景

  • 日志记录器:通常只需要一个全局的日志记录对象来记录应用的所有日志。
  • 数据库连接池:管理有限数量的数据库连接,保证连接的高效使用和共享。
  • 配置文件读取:读取配置信息的类,因为配置在整个应用中是全局且唯一的。
  • 线程池:管理和复用线程资源
  • 等等

🥲懒汉式-线程不安全

懒汉式-线程不安全-代码清单

/*** @author tiancx* @description: 懒汉式单例模式-线程不安全* @date 2024年07月20日*/
public class LazySingleton {private static LazySingleton lazySingleton;public LazySingleton(){}public static LazySingleton getInstance(){if(Objects.isNull(lazySingleton)){lazySingleton = new LazySingleton();}return lazySingleton;}
}

我们在测试类中运行 - 代码清单

import org.junit.jupiter.api.Test;import static org.junit.jupiter.api.Assertions.*;public class SingletonTest {@Testvoid getInstance() {for (int i = 0; i < 1000; i++) {new Thread(() -> {LazySingleton lazySingleton1 = LazySingleton.getInstance();LazySingleton lazySingleton2 = LazySingleton.getInstance();assertEquals(lazySingleton1, lazySingleton2);}).start();}}
}

🔔:这里得多试几次才可能看到这种问题,同时也说明这种写法是线程不安全的

image-20240720092550415

🙊懒汉式-线程安全

上面的懒汉式存在线程不安全问题,怎么解决呢,最简单的办法就是直接在getInstance方法前面加个锁synchronized

/*** @author tiancx* @description: 懒汉式单例模式-线程安全-效率低* @date 2024年07月20日*/
public class ThreadSafeLazySingleton {private static ThreadSafeLazySingleton threadSafeLazySingleton;public ThreadSafeLazySingleton(){}public static synchronized ThreadSafeLazySingleton getInstance(){if(Objects.isNull(threadSafeLazySingleton)){threadSafeLazySingleton = new ThreadSafeLazySingleton();}return threadSafeLazySingleton;}
}

🤓这种写法是没有问题的,不会有线程安全问题,但是直接锁住方法效率相对来说会比较低下

但是啊,计算机的大神特别的多啊,想着啊,人岂能无翻身之日,他们便想到了一个妙操作(双重检查机制

双重检查锁(Double-Checked Locking)是一种用于优化单例模式在多线程环境下性能的技术。

代码清单

/*** @author tiancx* @description: 懒汉式单例模式-线程安全-双重检查* @date 2024年07月20日*/
public class DoubleCheckSingleton {private static volatile DoubleCheckSingleton doubleCheckSingleton;public DoubleCheckSingleton(){}public static DoubleCheckSingleton getInstance(){if(Objects.isNull(doubleCheckSingleton)){synchronized (DoubleCheckSingleton.class){if(Objects.isNull(doubleCheckSingleton)){doubleCheckSingleton = new DoubleCheckSingleton();}}}return doubleCheckSingleton;}
}
  • 第一次检查(if (lazySingleton == null))是在同步代码块之外进行的。如果实例已经创建,那么直接返回,无需进入同步代码块,从而提高了性能。

  • 第二次检查(if (lazySingleton == null))是在同步代码块内部进行的。这是因为可能会有多个线程同时通过了第一次检查,进入到同步代码块中。通过第二次检查,确保只有一个线程能够创建实例。

  • 之所以需要双重检查,是因为如果只进行一次检查(在同步代码块内部),那么每次获取实例都需要进行同步,性能较差。而只进行外部的一次检查,又无法保证线程安全,可能会出现多个线程创建多个实例的情况。

通过双重检查,既保证了线程安全,又在一定程度上提高了性能

需要注意的是,为了确保线程之间对 lazySingleton 变量的可见性,需要将其声明为 volatile 类型

天上飞的理论,要有落地的实现

光在说性能咋好咋好,谁知道好不好呢

现在请拿起你们的键盘,打开你们的代码编辑器,开始写代码

    @Testvoid getThreadSafeLazyInstance() throws InterruptedException {final int threadCount = 100000; // 减少线程数量以减轻资源负担CountDownLatch latch = new CountDownLatch(threadCount);long start = System.currentTimeMillis();for (int i = 0; i < threadCount; i++) {CountDownLatch finalLatch = latch;new Thread(() -> {ThreadSafeLazySingleton instance1 = ThreadSafeLazySingleton.getInstance();ThreadSafeLazySingleton instance2 = ThreadSafeLazySingleton.getInstance();assertEquals(instance1, instance2);finalLatch.countDown(); // 通知CountDownLatch一个线程已经完成}).start();}latch.await(); // 等待所有线程完成long end = System.currentTimeMillis();System.out.println("ThreadSafeLazySingleton: " + (end - start) + "ms");// 重置CountDownLatch并测试第二个单例latch = new CountDownLatch(threadCount);start = System.currentTimeMillis();for (int i = 0; i < threadCount; i++) {CountDownLatch finalLatch1 = latch;new Thread(() -> {DoubleCheckSingleton instance1 = DoubleCheckSingleton.getInstance();DoubleCheckSingleton instance2 = DoubleCheckSingleton.getInstance();assertEquals(instance1, instance2);finalLatch1.countDown();}).start();}latch.await();end = System.currentTimeMillis();System.out.println("DoubleCheckLockingSingleton: " + (end - start) + "ms");}

image-20240720104734299

运行多次,结果均表明:双重检查锁的耗时要低一些,效率高一些

img

💓饿汉式

饿汉式是在类加载时就创建实例,简单高效,但可能造成资源浪费

这个代码就比较简单了,容易理解

代码清单

/*** @author tiancx* @description: 饿汉式单例模式* @date 2024年07月20日*/
public class EagerSingleton {private static final EagerSingleton EAGER_SINGLETON = new EagerSingleton();private EagerSingleton(){}public static EagerSingleton getInstance(){return EAGER_SINGLETON;}
}

💖枚举实现

Joshua Bloch 大神说过的这么一句话:单元素的枚举类型已经成为实现Singleton的最佳方法

枚举类型在 Java 中本身就保证了实例的唯一性,并且在序列化和反序列化过程中也能保持单例的特性,所以枚举也是实现单例的一种不错的方式

枚举实现单例模式的优点在于

简洁、线程安全,并且能够防止通过反射和序列化破坏单例

代码清单

public enum EnumSingleton {INSTANCE;private String url;private String username;private String password;// 构造函数私有化,防止外部创建实例private EnumSingleton() {//从配置文件中读取数据库连接信息//这里就不写了}
}

我么需要使用时直接EnumSingleton.INSTANCE然后写对应的字段就行了(EnumSingleton.INSTANCE.getUrl()),是不是特别方便吖~

💡 优点:

  • 线程安全:枚举类型本身的实现机制保证了在多线程环境下的安全性,无需额外的同步措施。
  • 防止反序列化破坏单例:由于枚举类的特殊机制,无法通过反序列化创建新的实例,从而保证了单例的唯一性。
  • 简洁直观:实现简单,代码量少,易于理解和维护。
  • 避免反射攻击:反射机制也无法破坏枚举单例的唯一性。

🍇容器实现

老板太棒啦,都看到这里来啦,这里也是重磅环节额,看了这个,你将会学会Spring框架IOC的核心原理,这里带你写一个简洁版的IOC,学会了也能吊da面试官额😏😏😏~

这里写的简洁版,写复杂了不好理解

image-20240720211539900

自定义注解-Component-代码清单

/*** @author tiancx* @description: 自定义注解:* @date 2024年07月20日*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {
}

UserService-代码清单

/*** @author tiancx* @description: 金钱服务-单例模式* @date 2024年07月20日*/
@Component
@Slf4j
public class MoneyService {/*** 充值*/public void add() {log.info("充值");}/*** 提现*/public void delete() {log.info("提现");}
}

SingletonBeanRegistry-代码清单

/*** @author tiancx* @description: 单例bean注册接口* @date 2024年07月20日*/
public interface SingletonBeanRegistry {Object getSingleton(String beanName);void addSingleton(String beanName, Object singletonObject);
}

DefaultSingletonBeanRegistry - 代码清单

/*** @author tiancx* @description: 单例bean注册* @date 2024年07月20日*/
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();@Overridepublic Object getSingleton(String beanName) {return singletonObjects.get(beanName);}@Overridepublic void addSingleton(String beanName, Object singletonObject) {singletonObjects.put(beanName, singletonObject);}
}

BeanFactory - 代码清单

/*** @author tiancx* @description: Bean工厂* @date 2024年07月20日*/
public interface BeanFactory {/*** 获取bean* @param beanName bean名称* @return bean 对象*/Object getBean(String beanName) throws Exception;/*** 获取bean* @param requiredType bean类型* @return bean 对象* @param <T> bean类型* @throws Exception bean不存在时抛异常*/<T> T getBean(Class<T> requiredType) throws Exception;/*** 是否包含bean* @param name bean名称* @return 是否包含*/boolean containsBean(String name);
}

AbstractBeanFactory - 代码清单

/*** @author tiancx* @description: Bean工厂* @date 2024年07月20日*/
public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory {@Overridepublic Object getBean(String beanName) throws Exception {return getSingleton(beanName);}@Overridepublic <T> T getBean(Class<T> requiredType) throws Exception {Object bean = getBean(requiredType.getName());return (T) bean;}@Overridepublic boolean containsBean(String name) {return false;}}

AnnotationApplicationContext - 代码清单


/*** @author tiancx* @description: 应用上下文* @date 2024年07月20日*/
public class AnnotationApplicationContext extends AbstractBeanFactory {/*** 扫描需要注册的bean*/public AnnotationApplicationContext(Set<Class<?>> classes) throws InstantiationException, IllegalAccessException {for (Class<?> target : classes) {if (target.isAnnotationPresent(Component.class)) {BeanDefinition definition = new BeanDefinition();definition.setBeanClass(target);definition.setInitMethodName(target.getSimpleName());addSingleton(target.getName(), target.newInstance());}}}
}

测试类

 @Testvoid testIoc() throws Exception {//找包下的类String packageName = "cn.xin.learn.design.creational.singleton";AnnotationApplicationContext context = new AnnotationApplicationContext(ClassScanner.scanPackage(packageName));Object bean = context.getBean(UserService.class.getName());Assertions.assertEquals(UserService.class, bean.getClass());UserService service = context.getBean(UserService.class);Assertions.assertEquals(service, bean);}

OK吖,这个容器版本的单例是不是很容易理解吖

这里为啥是单例呢,因为是从hashMap里面取的,只要key相同,取的都是同一个~

OK OK,把这些都给面试官讲完后,面试官一定会被你征服,夸你小可爱涅~

【都看到这了,点点赞点点关注呗,爱你们】😚😚

蓝白色微信公众号大学生校园清新简单纸飞机动态引导关注简洁新媒体分享中文动态引导关注

💬

✨ 正在努力的小叮当~
💖 超级爱分享,分享各种有趣干货!
👩‍💻 提供:模拟面试 | 简历诊断 | 独家简历模板
🌈 感谢关注,关注了你就是我的超级粉丝啦!
🔒 以下内容仅对你可见~

作者:小叮当撩代码CSDN后端领域新星创作者 |阿里云专家博主

CSDN个人主页:小叮当撩代码

🔎GZH哆啦A梦撩代码

🎉欢迎关注🔎点赞👍收藏⭐️留言📝

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

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

相关文章

Go语言之参数传递

文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 修改参数 假设你定义了一个函数&#xff0c;并在函数里对参数进行…

机器人开源调度系统OpenTcs6-架构运行分析

系统启动 启动 Kernel&#xff1a;加载核心应用&#xff0c;初始化系统配置和状态。 启动 Plant Overview&#xff1a;加载图形用户界面&#xff0c;初始化模型和用户界面。 模型导入和配置 在 Plant Overview 中导入或创建工厂布局模型。 配置路径、位置和车辆信息。 车辆连…

C语言 | Leetcode C语言题解之第240题搜索二维矩阵II

题目&#xff1a; 题解&#xff1a; bool searchMatrix(int** matrix, int matrixSize, int* matrixColSize, int target){int i 0;int j matrixColSize[0] - 1;while(j > 0 && i < matrixSize){if(target < matrix[i][j])j--;else if(target > matrix[…

达梦数据库的系统视图v$mal_link_status

达梦数据库的系统视图v$mal_link_status 在达梦数据库&#xff08;DM Database&#xff09;中&#xff0c;V$MAL_LINK_STATUS 是一个动态性能视图&#xff08;Dynamic Performance View&#xff09;&#xff0c;用于显示MAL&#xff08;Multi-threaded Architecture Link&…

cs224w笔记(p5)

链接预测任务的两种类型&#xff1a;随机缺失边&#xff1b;随时间演化边。 第一种假设可以以蛋白质之间的交互作用举例&#xff0c;缺失的是研究者还没有发现的交互作用。 第二种假设可以以社交网络举例&#xff0c;随着时间流转&#xff0c;人们认识更多朋友。 基于相似性进…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第三十六章 Linux驱动初探

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

Android 小白菜鸟从入门到精通教程

前言 Android一词最早出现于法国作家利尔亚当&#xff08;Auguste Villiers de l’Isle-Adam&#xff09;在1886年发表的科幻小说《未来的夏娃》&#xff08;L’ve future&#xff09;中。他将外表像人的机器起名为Android。从初学者的角度出发&#xff0c;通过通俗易懂的语言…

使用 useLazyAsyncData 提升数据加载体验

title: 使用 useLazyAsyncData 提升数据加载体验 date: 2024/7/19 updated: 2024/7/19 author: cmdragon excerpt: 摘要&#xff1a;本文介绍useLazyAsyncData函数在Nuxt 3中的使用&#xff0c;以提升数据加载体验。此函数支持异步获取数据并在组件中处理挂起与错误状态&…

[网鼎杯 2018]Fakebook

解法一 在robots.txt&#xff0c;可以发现/user.php.bak 下载下来是一段代码 <?phpclass UserInfo {public $name "";public $age 0;public $blog "";public function __construct($name, $age, $blog){$this->name $name;$this->age (…

spring-boot 整合 redisson 实现延时队列(文末有彩蛋)

应用场景 通常在一些需要经历一段时间或者到达某个指定时间节点才会执行的功能&#xff0c;比如以下这些场景&#xff1a; 订单超时提醒收货自动确认会议提醒代办事项提醒 为什么使用延时队列 对于数据量小且实时性要求不高的需求来说&#xff0c;最简单的方法就是定时扫描数据…

电机泵盖机器人打磨去毛刺,选德国进口高精度主轴

机器人打磨去毛刺该如何选择主轴呢&#xff1f;首先我们需要考虑的是工件的材质&#xff0c;电机泵盖通常使用铸铁、不锈钢、合金钢等金属材质&#xff0c;因此这类保持的硬度较高&#xff0c;一般会选择功率、扭矩较大的德国进口高精度主轴Kasite 4060 ER-S。 Kasite 4060 ER-…

【Espressif-ESP32S3】【VScode】安装【ESP-IDF】插件及相关工具链

一、ESP-IDF简介 二、VScode安装ESP-IDF插件 三、安装ESP-IDF、ESP-IDF-Tools以及相关工具链 四、测试例程&编译烧录 五、IDF常用指令 资料下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/15Q2rl2jpIaKfj5rATkYE6g?pwdGLNG 提取码&#xff1a;GLNG 一、ESP-…

浏览器缓存:强缓存与协商缓存实现原理有哪些?

1、强缓存&#xff1a;设置缓存时间的&#xff0c;那么在这个时间内浏览器向服务器发送请求更新数据&#xff0c;但是服务器会让其从缓存中获取数据。 可参考&#xff1a;彻底弄懂强缓存与协商缓存 - 简书 2、协商缓存每次都会向浏览器询问&#xff0c;那么是怎么询问的呢&…

「MQTT over QUIC」与「MQTT over TCP」与 「TCP 」通信测试报告

一、结论 在实车5G测试中「MQTT Over QUIC」整体表现优于「TCP」&#xff0c;可在系统架构升级时采用MQTT Over QUIC替换原有的TCP通讯&#xff1b;从实现原理上基于QUIC比基于TCP在弱网、网络抖动导致频繁重连场景延迟更低。 二、测试方案 网络类型&#xff1a;实车5G、实车…

【Apache Doris】周FAQ集锦:第 14 期

【Apache Doris】周FAQ集锦&#xff1a;第 14 期 SQL问题数据操作问题运维常见问题其它问题关于社区 欢迎查阅本周的 Apache Doris 社区 FAQ 栏目&#xff01; 在这个栏目中&#xff0c;每周将筛选社区反馈的热门问题和话题&#xff0c;重点回答并进行深入探讨。旨在为广大用户…

【VUE】v-if和v-for的优先级

v-if和v-for v-if 用来显示和隐藏元素 flag为true时&#xff0c;dom元素会被删除达到隐藏效果 <div class"boxIf" v-if"flag"></div>v-for用来进行遍历&#xff0c;可以遍历数字对象数组&#xff0c;会将整个元素遍历指定次数 <!-- 遍…

Django 执行原生SQL

在Django中&#xff0c;你可以使用Raw SQL queries来执行原生的SQL查询。这对于需要进行复杂查询或Django的ORM无法满足的查询非常有用。 1&#xff0c;添加模型 Test/app11/models.py from django.db import modelsclass Post(models.Model):title models.CharField(max_le…

前端Vue组件技术实践:构建自定义动态宫格菜单按钮组件

随着前端技术的不断发展&#xff0c;复杂度和开发难度也随之增加。传统的整体式开发方式已经难以满足现代前端应用的需求&#xff0c;特别是在业务场景复杂、产品迭代频繁的情况下。组件化开发作为一种有效的解决方案&#xff0c;通过拆分和组合独立的组件&#xff0c;实现了单…

【Nacos】Nacos服务注册与发现 心跳检测机制源码解析

在前两篇文章&#xff0c;介绍了springboot的自动配置原理&#xff0c;而nacos的服务注册就依赖自动配置原理。 Nacos Nacos核心功能点 服务注册 :Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务&#xff0c;提供自身的元数据&#xff0c;比如ip地址、端…

19.x86游戏实战-创建MFC动态链接库

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…