【多线程】单例模式

🏀🏀🏀来都来了,不妨点个关注!
🎧🎧🎧博客主页:欢迎各位大佬!
在这里插入图片描述

文章目录

  • 1. 什么是单例模式
    • 1.1 理解单例模式
    • 1.2 单例模式的特点
  • 2. 饿汉模式
  • 3. 懒汉模式
  • 3.1 单线程下的懒汉模式
    • 3.2 多线程下的懒汉模式
      • 3.2.1 多线程下的懒汉模式(优化)
      • 3.2.2 多线程下的单例模式(双重检验锁)

1. 什么是单例模式

单例模式是一种经典的设计模式它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。 单例模式主要用于控制对某些共享资源的访问,例如配置管理器、连接池、线程池、日志对象等。
这里我们提到了设计模式,我们就先介绍下什么是设计模式,下过棋的同学应该都知道什么是棋谱,就是一些固定的下棋套路,这些下棋套路可能不算特别好,但也不会特别差,或者经常玩云顶的小伙伴也知道阵容这个说法,我刚开始接触云顶的时候不怎么会玩,就会去网上搜索各种阵容套路,这其实也是一种设计模式,它说不上特别好,但也不会特别差,不会让我们老八出门。
设计模式:相当于软件开发中的“棋谱”,针对很多常见的 "问题场景"总结出的一些固定的套路. 按照这个套路来实现代码, 不会吃亏。
我们今天要介绍的单例模式就是其中的一种设计模式,下面我们介绍两种最常见的单例模式,分别是饿汉模式懒汉模式

1.1 理解单例模式

在介绍之前我们先举个生活中的小例子来大致了解下什么是饿汉模式什么是懒汉模式:对于吃饭后什么时候洗碗这件事,小丁和小万有着截然不同的观点,小丁本着 非必要不洗碗(懒汉模式) 的观点,每次吃完饭之后就会把碗放在一旁去干其他事了,等下次什么时候吃饭了再来洗碗。而小万就不一样了,她很勤快,每次吃完饭后就急迫着去洗碗(饿汉模式),不管下次啥时候吃饭,先把碗洗好再说。

1.2 单例模式的特点

在具体写代码实现单例模式之前我们需要想了解单例模式的特点:

  1. 私有构造方法:确保外部代码不能通过构造器创建类的实例。
  2. 私有静态实例变量:持有类的唯一实例。
  3. 公有静态方法:提供全局访问点以获取实例,如果实例不存在,则在内部创建。

2. 饿汉模式

饿汉式单例在类加载时就急切地创建实例,不管你后续用不用得到,这也是饿汉式的来源,简单但不支持延迟加载实例。
具体代码实现:

//饿汉模式
class Singleton{//唯一实例的本体private static Singleton singleton = new Singleton();//公有获取到实例的方法public static Singleton getSingleton() {return singleton;}//将构造方法封装为私有的,外部不能创建新的对象private Singleton() {}}
public class ThreadDemo18 {public static void main(String[] args) {Singleton s1 = Singleton.getSingleton();Singleton s2 = Singleton.getSingleton();//s1 和 s2 为同一对象,打印trueSystem.out.println(s1 == s2);}
}

这里我们运行可以看到s1和s2对象是一样的:
在这里插入图片描述
在饿汉模式下,即使有多个线程同时调用getSingleton()方法也不会有线程安全的问题,这是因为这里只涉及到读取数据的情况,但多线程下,懒汉模式可能就无法保证创建对象的唯一性了,下面我们就来重点介绍下懒汉模式。

3. 懒汉模式

类加载的时候不创建实例. 第一次使用的时候才创建实例.

3.1 单线程下的懒汉模式

在写具体的单线程的懒汉模式之前我们需要先清楚懒汉模式,既然叫懒汉,突出一个字——懒,即非必要,不加载。 那代码也就很理所当然了,只有在你第一次调用该类的时候,才会去创建对象,我们就可以在对外提供的公有的非静态方法中进行判断,当该对象为空的时候才去创建对象,代码如下:

class  SingletonLazy {private static SingletonLazy instance = null;public static SingletonLazy getSingletonLazy() {if (instance == null) {instance = new SingletonLazy();}return instance;}private SingletonLazy() {}
}
public class ThreadDemo19 {public static void main(String[] args) {SingletonLazy s1 = SingletonLazy.getSingletonLazy();SingletonLazy s2 = SingletonLazy.getSingletonLazy();System.out.println(s1 == s2);}
}

运行结果:
在这里插入图片描述

3.2 多线程下的懒汉模式

为什么会有多线程下的懒汉模式,肯定是因为单线程下的懒汉模式在多线程的环境中会出现线程安全的问题嘛,这个我们可以从上面单线程下的懒汉模式在多线程环境下可能会出现的问题进行解释,如下图:

在这里插入图片描述
在之前的线程安全文章中,我们提到过,线程不安全的罪魁祸首是CPU的调度是无序的,是抢占式执行的,所以在上述代码逻辑中,就会出现以下安全问题:t1线程判断instance为空,会new出来一个对象,接下来,CPU调度资源分配给了t2线程,此时t2线程也判断instance为空,会new出来一个对象,这样就会导致new出来了两个对象。到这里很多人可能不以为然,不就多new出来一个对象嘛,多大点事。但如果这个对象特别大呢,比如100G,这样就对资源造成了很大的消耗,并且也不满足我们的初心一个类只有一个实例对象。
那如何解决呢,在之前的线程安全的文章中我们也提到过类似的问题,那就加锁嘛,将if判断instance为空和new对象这个操作进行加锁保证它是一个原子性的操作就好了嘛,那代码就很理所应当了,如下:

class SingletonLazy{private static SingletonLazy  singletonLazy = null;public static SingletonLazy getSingletonLazy() {synchronized (SingletonLazy.class) {if (singletonLazy == null) {singletonLazy = new SingletonLazy();}}return singletonLazy;}private  SingletonLazy() {}
}

看到这里,很多人肯定就以为就完事了,但其实我们需要知道一个事,加锁其实是很低效的事情,加锁可能就会涉及到线程的阻塞等待,所以我们希望的是非必要,不加锁。 再看我们上述的代码,每个线程来调用这个getSingletonLazy()方法来获取单例对象的时候都需要进行加锁,这就导致了一个问题,当很多线程同时来获取这个单例对象的时候,当一个线程t1获取到锁之后,其他线程只能进行阻塞等待,当t1线程执行完之后,接着一个t2线程获取到锁,其他线程也需要阻塞等待,非常的低效。这就类似我们排队买吃的一样,当前面的人在买早餐的时候,我们只能在后面慢慢排队等待。

3.2.1 多线程下的懒汉模式(优化)

我们回想一下我们对该地方进行加锁的初心,是在第一次实例化这个对象的时候,由于CPU的调度,可能会导致两个线程都会判断instance为空,从而实例化两个对象出来,那我们就可以在这里做个小优化了:只有当instance对象为空的时候,即还没有创建过该对象的时候我们才进行加锁, 因为只能在第一次实例化该对象的时候会出现线程安全的问题,后面有了该对象之后我们只涉及到读取这个对象的操作不涉及到修改操作就不会产生线程安全的问题。
代码实现如下:

class SingletonLazy{private static SingletonLazy  singletonLazy = null;public static SingletonLazy getSingletonLazy() {if (singletonLazy == null) {synchronized (SingletonLazy.class) {if (singletonLazy == null) {singletonLazy = new SingletonLazy();}}}return singletonLazy;}private  SingletonLazy() {}
}

3.2.2 多线程下的单例模式(双重检验锁)

看过我之前写的线程安全的文章的小伙伴应该都知道,上面new对象的操作也可能会涉及到指令重排序的问题,以此,我们在这里也需要对new对象这个操作进行下改进。对instance对象加上volatile关键字修饰即可。
new对象操作大致分为以下三步:

  1. 给该对象分配内存空间
  2. 调用构造方法,进行初始化
  3. 将内存地址指向引用地址

代码如下:

class SingletonLazy{volatile private static SingletonLazy  singletonLazy = null;public static SingletonLazy getSingletonLazy() {if (singletonLazy == null) {synchronized (SingletonLazy.class) {if (singletonLazy == null) {singletonLazy = new SingletonLazy();}}}return singletonLazy;}private  SingletonLazy() {}
}

这也是面试官经常会考的问题:让你手写一个单例模式,并解释下双重检验锁使用单例模式的原理。
关键点解释
volatile关键字
确保多个线程能够正确处理instance变量。volatile禁止了指令重排序优化,保证了在多线程环境下变量的可见性。即当一个线程修改了某个变量的值,新值对其他线程来说是立即可见的。
双重检查
第一次检查是在方法级别进行的,如果instance不为null,则直接返回实例,避免不必要的同步开销。
第二次检查是在同步块内部进行的,这是为了防止多个线程同时进入同步块并创建多个实例。

今天的分享就结束了,感谢支持!

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

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

相关文章

中国人民解放军建军97周年

缅怀先烈,砥砺前行 付吾辈之韶华,耀吾辈之中华! 万里河山,有您心安!

Django REST Framework(十五)路由Routes

如何在Django REST framework中利用SimpleRouter和DefaultRouter来高效生成视图集的路由信息,并详细解释如何使用action装饰器为视图集中的自定义方法生成路由 1.路由的定义规则 路由称为URL(Uniform Resource Locator,统一资源定位符),也可以称为URLconf,是对可以从互联…

【Java题解】杨辉三角—力扣

🎉欢迎大家收看,请多多支持🌹 🥰关注小哇,和我一起成长🚀个人主页🚀 ⭐目前主更 专栏Java ⭐数据结构 ⭐已更专栏有C语言、计算机网络⭐ 题目链接:杨辉三角 目录👑 ⭐题…

the request was rejected because no multipart boundary was found

文章目录 1. 需求描述2. 报错信息3. 探索过程1. 使用postman 排除后端错误2. 搜索网上的解决方法3. 解决方法 1. 需求描述 想要在前端上传一个PDF 发票,经过后端解析PDF之后,将想要的值自动回填到对应的输入框中 2. 报错信息 org.apache.tomcat.util.…

2024年有哪些开放式耳机值得入手?值得关注的开放式耳机评测大赏

如今,开放式耳机越来越受到人们的关注。2024 年更是涌现出了众多优秀的开放式耳机产品。但在众多选择面前,哪一款耳机的音质更出色?哪一款佩戴起来更舒适?又有哪一款在通话质量和连接性能上表现更优异呢?接下来我将详细…

【Devops】CertD 完全免费、自动申请、自动部署SSL证书一站式管理工具 | 自动化HTTPS | 3个月SSL自动轮换

CertD CertD 是一个免费全自动申请和自动部署更新SSL证书的工具。 后缀D取自linux守护进程的命名风格,意为证书守护进程。 关键字:证书自动申请、证书自动更新、证书自动续期、证书自动续签 一、特性 本项目不仅支持证书申请过程自动化,还…

SpringMVC源码解析(二):请求执行流程

SpringMVC源码系列文章 SpringMVC源码解析(一):web容器启动流程 SpringMVC源码解析(二):请求执行流程 目录 前言DispatcherServlet入口一、获取HandlerExcutionChain(包括Handler)1、获取Handler1.1、通过request获取查找路径1.2、通过查找路径获取Han…

找工作很迷茫?程序员的岗位宝典来了!

随着数字化转型进展深入,大量数字化、智能化的岗位相继涌现。 但即使这样,大家依然认为,找到一份合适的工作实在是太!难!了! 调查显示,技术创新和商业模式正在成为助推企业发展的两大动力。同时…

【iOS】——锁

五类锁 锁作为一种非强制的机制,被用来保证线程安全。每一个线程在访问数据或者资源前,要先获取(Acquire)锁,并在访问结束之后释放(Release)锁。如果锁已经被占用,其它试图获取锁的…

计算机网络必会面经

1.键入网址到网页显示,期间发生了什么 2.在TCP/IP网络模型中。TCP将数据进行分段后,为什么还需要IP层继续分片 3.详细说明tcp三次握手,为什么是三次,若每次握手丢了,解决办法是什么 4.详细说明tcp四次挥手&#xff…

【Python】python基础

本篇文章将讲解以下知识点: (1)循环语句 (2)字符串格式化 (3)运算符 一:循环语句 循环语句有两种:while for 本篇文章只讲解while循环 格式: whil…

Unity材质球自动遍历所需贴图

Unity材质球自动遍历所需贴图 文章目录 Unity材质球自动遍历所需贴图一、原理二、用法1.代码:2.使用方法 一、原理 例如一个材质球名为:Decal_Text_Cranes_01_Mat , 然后从全局遍历出:Decal_Text_Cranes_01_Albedo赋值给材质球的…

【网络基础】初识网络 {计算机网络背景;网络协议初识;网络传输基本流程;网络中的地址管理;网络设备简单介绍}

一、计算机网络背景 1.1 网络发展 计算机网络的发展可以追溯到20世纪60年代,那时候最初的计算机网络只是为了让科学家们能够共享计算机资源和数据。但是在20世纪80年代,互联网的出现彻底改变了计算机网络的面貌,使得人们可以随时随地通过互…

AI剪辑短视频以及账号管理矩阵工具系统搭建开发

目录 前言 一、系统有哪些功能? 二、怎么开发 前言 通过AI剪辑短视频以及生成短视频,以及对自媒体账号的管理功能的功能进行开发。这款系统能够批量混合剪辑视频然后一键发布到绑定好的自媒体账号里面。 一、系统有哪些功能? 1.AI智能文…

【深度学习实战(49)】目标检测损失函数:IoU、GIoU、DIoU、CIoU、EIoU、alpha IoU、SIoU、WIoU原理及Pytorch实现

前言 损失函数是用来评价模型的预测值和真实值一致程度,损失函数越小,通常模型的性能越好。不同的模型用的损失函数一般也不一样。损失函数主要是用在模型的训练阶段,如果我们想让预测值无限接近于真实值,就需要将损失值降到最低…

深入探讨RCE漏洞及其防御策略

1. RCE漏洞 1.1. 漏洞原理 远程代码执行(RCE)漏洞允许攻击者远程注入并执行操作系统命令或代码,从而控制后台系统。 1.2. 漏洞产生条件 调用第三方组件存在代码执行漏洞。用户输入内容作为系统命令参数拼接到命令中。对用户输入的过滤不严…

汽车雷达系统集成

汽车雷达系统集成是实现高级驾驶辅助系统(ADAS)和自动驾驶功能的重要环节,它涉及多种硬件和软件的协同工作。以下将详细讲解汽车雷达系统集成的各个方面: 雷达传感器选择 毫米波雷达:毫米波雷达主要使用24GHz和77GHz频…

【SQL Server点滴积累】SQL Server 2016数据库邮件(Database Mail)功能故障的解决方法

今天和大家分享SQL Server 2016数据库邮件(Database Mail)功能故障的解决方法 故障现象: 在SQL Server 2016中配置完成数据库邮件(Database Mail)功能后,当你尝试发送测试邮件后,既收不到测试邮件,也不显示错误消息 KB3186435 -…

Windows + Ubuntu双系统!小白轻松安装

前言 这几天有小伙伴想着装WindowsUbuntu双系统,但苦于找不到办法,就在某篇文章后台留言: 这不,今天就更新了嘛!虽然做不到有求必应,但教程帖还是可以写写的,能帮一个是一个! 今天要…

FFmpeg:多媒体处理的瑞士军刀

😎 作者介绍:欢迎来到我的主页👈,我是程序员行者孙,一个热爱分享技术的制能工人。计算机本硕,人工制能研究生。公众号:AI Sun(领取大厂面经等资料),欢迎加我的…