什么是单例模式?

  前言👀~

上一章我们介绍了多线程下引发的安全问题,今天接着讲解多线程的内容,同样很重要,请细品

单例模式

饿汉模式

懒汉模式

饿汉模式和懒汉模式的区别

再遇线程不安全问题

指令重排序问题


如果各位对文章的内容感兴趣的话,请点点小赞,关注一手不迷路,讲解的内容我会搭配我的理解用我自己的话去解释如果有什么问题的话,欢迎各位评论纠正 🤞🤞🤞

12b46cd836b7495695ce3560ea45749c.jpeg

个人主页:N_0050-CSDN博客

相关专栏:java SE_N_0050的博客-CSDN博客  java数据结构_N_0050的博客-CSDN博客  java EE_N_0050的博客-CSDN博客


单例模式

开发过程中,会遇到很多经典场景,就是经常出现这种场景,针对这些频繁出现的场景,提出了这种设计模式,遇到什么场景我们就用什么设计模式

1.单例模式(重要):非常经典的设计模式(我们需要掌握的技能)

单例就是指单个实例也就是单个对象,有些场景我们希望有的类只能有一个对象,不能有多个,这样的场景下,只能使用单例模式。例如按常理说每个男人或者女人只能有一个老公或者老婆。代码中很多用于管理数据的对象,就是单例的,例如Mysql JDBC中的DataSource(描述了mysql服务器的位置),JDBC 中的 DataSource 实例就只需要一个实例

像单例模式这样的思想,在很多地方有体现,例如final修饰的常量修改了会报错,以及接口实现了接口就必须重写里面的所有方法不然会报错等。但是呢,在语法上,没有对单例做出支持,只能通过编程技巧来达成类似的效果,此时我们需要编译器帮我们做出监督,如果创建出多个对象,编译器直接报错

下面我们设计一个单例模式:

1.在类的内部,提供一个获取当前类的实例

2.使用private修饰构造方法,避免其他类创建出这个类的实例

class Singleton {public static Singleton instance = new Singleton();public static Singleton getInstance() {return instance;}private Singleton() {}
}

这样设计后,别人只能通过这个类的实例使用这个类,不能创建这个类的实例


饿汉模式

单例模式具体的实现方式, 分成 "饿汉" 和 "懒汉" 两种

饿汉模式:创建时机比较早,在类加载的时候就创建了因为这里使用了static修饰,我们知道static修饰的成员在类加载的时候就创建了,相当于你三天没吃饭了,看到吃的立马冲上去哐哐吃

和上面我们设计的单例模式一样,那个代码就是饿汉模式的代码实现

class Singleton {public static Singleton instance = new Singleton();public static Singleton getInstance() {return instance;}private Singleton() {}
}

懒汉模式

懒汉模式:创建时机比饿汉模式晚,在第一次使用的时候也就是调用获取实例的方法,才去创建实例,没调用就没创建

下面是实现懒汉模式的代码

class SingletonLazy {public static SingletonLazy instance = new SingletonLazy();public static SingletonLazy getInstance() {if (instance == null) {instance = new SingletonLazy();}return instance;}private SingletonLazy() {}
}

饿汉模式和懒汉模式的区别

下面我举了两个例子

例子1:拿程序举个例子,就比如我们打开一个笔记软件(20个G),有两种情况,一种是一下子把所有数据都加载到内存中,加载完后才显示内容,这个加载的过程很慢(饿汉)。还有一种就是加载一部分到内存,你打开就能看到一部分内容,然后你想看其他地方的内容你一点击,它就会加载到内存然后显示内容(懒汉)

例子2:比如懒汉模式就是我们回到家想着要去洗澡,但是呢有点累想休息会再去洗,于是打开了手机在那刷短视频,一刷就停不下来了,直到你发现时间很晚了才去洗。饿汉模式就是我们回到家直接就去洗澡,即使你很累,但是对于有些人它会立马去洗澡再去干其他事。对于我来说,我就属于懒汉模式,我觉得大多数人都是懒汉模式


再遇线程不安全问题

之前说过多个线程,同时修改同一变量(共享变量),此时就可能出现线程不安全的问题,多个线程,同时读取一个变量,这个是没事的

多线程情况下,饿汉模式线程安全因为它的实例在类加载的时候就创建了,其他类使用的时候直接调用方法去使用即可(直接读取即可),但是呢,多线程情况下,懒汉模式可能会造成线程不安全(又读又修改),下图进行演示

解释:懒汉模式的实例在获取实例的时候才会创建,可能会导致t1这个线程获取实例的时候然后进行判断,此时t2线程已经创建了实例,然后轮到t1线程接着执行后面的代码,又创建了一个实例,这就违背了单例模式的要求

如何去解决呢?想到的办法肯定是加锁,这样就能保证在一个线程在创建实例的时候,另外一个线程进入阻塞等待(锁的互斥)下面是加锁的代码

    public static SingletonLazy getInstance() {synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}return instance;}}

但是呢又会有个问题就是加锁,导致我们的性能降低,并且懒汉模式线程安全问题只是出现在最开始还没创建出实例的时候,,创建出来了就没有这样的问题了。所以每次获取实例的时候都要加锁,虽然解决了线程不安全的问题,但是性能不高,加锁和解锁是一个开销很大的操作,并且加锁可能会涉及到锁冲突/锁竞争问题,冲突就会造成阻塞等待

解决方法:加个if语句进行判断,这样就算一个线程在cpu上执行一半被换成其他线程也没事,其他线程创建完实例后,再次回到cpu上执行的时候,进入加锁然后判断,此时已经有实例了,直接返回实例即可,因为我们知道对同一把锁加锁两次会造成锁冲突,导致另外一个线程进入阻塞,这时候两个if就起了重要作用,下面是代码演示

    public static SingletonLazy getInstance() {if (instance == null) {synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}

指令重排序问题

指令重排序:也是编译器优化的问题,编译器为了执行效率,可能会调整当前代码的执行顺序,但是调整的前提是保持逻辑不变。注意一下这里说的编译器是java中的编译器

什么意思?举个例子就比如你今天在学校完成这几件事吃饭、拿快递、跑步。你可以选择先吃饭再去拿快递再去吃饭,也可以选择先跑步再拿快递再去吃饭。但是呢你寝室离操场和快递站近,离食堂远,这时候选择先跑步再拿快递再去吃饭这样的效率更高。所以我们可以看出一般情况下,指令重排序在保持逻辑不变的情况下,可以使我们的执行效率提高。单线程没什么问题,但是多线程下可能会出现问题。我们单例模式中懒汉模式就会出现这种问题,看下面这段代码

class SingletonLazy {public static SingletonLazy instance = new SingletonLazy();public static SingletonLazy getInstance() {if (instance == null) {synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();//new对象的时候会进行优化}}}return instance;}private SingletonLazy() {}
}

指令重排序问题:在这段代码中编译器可能会对new操作创建对象操作进行优化,首先创建对象可以分为三步,第一步申请内存空间,第二步在内存上也可以说堆,在内存上创建对象然后进行初始化就是使用构造方法,第三步把内存的地址赋值给instance引用。对于创建对象这三个步骤的顺序是可以调整的,123或者132都行,但是第一步一定是先执行的,不然都没有内存空间你把对象搁哪呢?然后在t1线程中编译器开始操作先进行1和3步骤在准备要执行第二步的时候,然后这个时候cpu开始搞事情了执行t2线程,t2线程一进来不是加锁的操作而是if判断,这里注意一下锁的阻塞等待,一定是两个线程都加锁的的时候才会触发。所以可以执行if判断,然后判断直接返回instance引用。这时候这个引用指向了没有初始化的非法对象如果要调用方法或者属性可能会出bug
 

针对上述问题,我们使用volatile关键字来禁止编译器进行优化,这样指令重排序问题就解决了

class SingletonLazy {public static volatile SingletonLazy instance = new SingletonLazy();//使用volatile关键字public static SingletonLazy getInstance() {if (instance == null) {synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() {}
}

以上便是本章内容,单例模式在设计模式中还是中还是很常见的,虽然内容不多,但是有很多注意点要注意,我们下一章再见💕

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

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

相关文章

学习笔记(linux高级编程)11

进程间通信 》信号通信 应用:异步通信。 中断,, 1~64;32应用编程。 如何响应: Term Default action is to terminate the process. Ign Default action is to ignore the signal. wait Core Default action is …

ffmpeg将多个yuv文件编码为MP4视频文件

一、编码方案 在视频录制时,每一帧保存为一个yuv文件,便于纠错和修改。在编码为MP4文件时,我的方案是将所有yuv文件先转码为单个MP4文件,然后使用ffmpeg的concat功能拼接为完整的视频。 二、shell脚本 #!/bin/bash# 检查参数数量…

【Arduino】ESP8266开发环境配置(图文)

ESP8266与ESP32开发很类似,相当于是低配版本的ESP32,其同样具有无线网络连接能力,功能强大,而且价格比ESP32更具有优势。接下来我们就来设置一下ESP8266的开发环境。 使用Arduino开发平台软件,选择首选项进行设置。 h…

【C++】 ——【模板初阶】——基础详解

目录 1. 泛型编程 1.1 泛型编程的概念 1.2 泛型编程的历史与发展 1.3 泛型编程的优势 1.4 泛型编程的挑战 2. 函数模板 2.1 函数模板概念 2.2 函数模板格式 2.3 函数模板的原理 2.4 函数模板的实例化 2.5 模板参数的匹配原则 2.6 函数模板的特化 2.7 函数模板的使…

事务底层与高可用原理

1.事务底层与高可用原理 事务的基础知识 mysql的事务分为显式事务和隐式事务 默认的事务是隐式事务 显式事务由我们自己控制事务的开启,提交,回滚等操作 show variables like autocommit; 事务基本语法 事务开始 1、begin 2、START TRANSACTION&…

MySQL数据恢复(适用于误删后马上发现)

首先解释一下标题,之所以适用于误删后马上发现是因为太久了之后时间和当时操作的数据表可能会记不清楚,不是因为日志丢失 1.首先确保自己的数据库开启了binlog(我的是默认开启的我没有配置过) 根据这篇博客查看自己的配置和自己…

AI墓地:738个倒闭AI项目的启示

近年来,人工智能技术迅猛发展,然而,不少AI项目却在市场上悄然消失。根据AI工具聚合网站“DANG”的统计,截至2024年6月,共有738个AI项目停运或停止维护。本文将探讨这些AI项目失败的原因,并分析当前AI初创企…

前端跨域问题--解析与实战

引言 在现代网络应用中,跨域问题是一个常见的挑战。由于浏览器的同源策略,限制了从不同源(域名、协议或端口)进行资源共享,这就造成了跨域访问的限制。跨域资源共享(CORS)是一种技术&#xff0…

九、函数的声明和定义

函数声明: 1. 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数 声明决定不了。 2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用。 3. 函数的声明一般要放在头文件中的。 定义的函…

育才兴业,助力数字产业蓬勃发展

成都树莓集团通过校企合作、建设人才项目转化中心、推动一线多园战略以及提供全方位服务等举措,积极培养数字产业人才,为行业发展提供了有力支持。 一、校企合作,打造人才培养高地 树莓集团深知企业协同育人的重要性,积极与高校建…

某DingTalk企典 - Token

⚠️前言⚠️ 本文仅用于学术交流。 学习探讨逆向知识,欢迎私信共享学习心得。 如有侵权,联系博主删除。 请勿商用,否则后果自负。 网址 aHR0cHM6Ly9kaW5ndGFsay5jb20vcWlkaWFuLw 浅聊一下 没毛病,就这字段,有效期…

java考试题20道

选择题 编译Java源代码文件的命令是javac javac命令是将Java源代码文件进行编译得到字节码文件(.class文件) java命令是在JVM上运行得到的字节码文件 下面是一个示例: javac test.java -------> test.class java test ------> 运行test.class文件下列那…

#LinuxC高级 笔记二

makefile gcc gdb makefile 1. 分文件编程 1.1 源文件&#xff1a;.c结尾的文件 包含main函数的.c 包含子函数的.c 1.2 头文件&#xff1a;.h结尾的文件 头文件、宏定义、typedef 、结构体、共用体、枚举、函数声明 include引用时“”和<>的区别&#xff1a; <>去系…

复合机器人:手脚眼脑的完美结合

在现代工业制造的舞台上&#xff0c;复合机器人如同一位精密而高效的工匠&#xff0c;以其独特的手脚眼脑&#xff0c;正深刻改变着传统的生产方式。这些机器人不仅仅是机械臂的简单延伸&#xff0c;它们汇聚了先进的机械结构、智能的感知系统、精密的控制技术和灵活的思维能力…

【CSAPP】-linklab实验

目录 实验目的与要求 实验原理与内容 实验步骤 实验设备与软件环境 实验过程与结果&#xff08;可贴图&#xff09; 实验总结 实验目的与要求 1.了解链接的基本概念和链接过程所要完成的任务。 2.理解ELF目标代码和目标代码文件的基本概念和基本构成 3.了解ELF可重定位目…

安全和加密常识(6)Base64编码方式

文章目录 什么是 Base64编码原理编解码示例应用什么是 Base64 Base64 是一种用于将二进制数据编码为仅包含64种ASCII字符的文本格式的编码方法,注意,它不是加密算法。它设计的目的主要是使二进制数据能够通过只支持文本的传输层(如电子邮件)进行传输。Base64常用于在需要处…

Python | 基于支持向量机(SVM)的图像分类案例

支持向量机&#xff08;SVM&#xff09;是一种监督机器学习算法&#xff0c;可用于分类和回归任务。在本文中&#xff0c;我们将重点关注使用SVM进行图像分类。 当计算机处理图像时&#xff0c;它将其视为二维像素阵列。数组的大小对应于图像的分辨率&#xff0c;例如&#xf…

三菱PLC标签使用(I/O的映射)与内容

今天&#xff0c;小编继续开始三菱PLC的学习&#xff0c;今天的内容是标签及其标签的内容说明&#xff0c;如果对你有帮助&#xff0c;欢迎评论收藏。 标签的种类&#xff0c;等级&#xff0c;定义 种类 三菱3U的PLC的种类分别为二种&#xff1a;全局标签与局部标签 全局标签…

RabbitMQ-交换机的类型以及流程图练习-01

自己的飞书文档:‌‍‬‍‬‍​‍‬​⁠‍​​​‌⁠​​‬‍​​​‬‬‌​‌‌​​&#xfeff;​​​​&#xfeff;‍​‍​‌&#xfeff;⁠‬&#xfeff;&#xfeff;&#xfeff;​RabbitMQ的流程图和作业 - 飞书云文档 (feishu.cn) 作业 图片一张 画rabbit-mq 消息发…

【HDC.2024】探索无限可能:华为云区块链+X,创新融合新篇章

6月23日&#xff0c;华为开发者大会2024&#xff08;HDC 2024&#xff09;期间&#xff0c; “「区块链X」多元行业场景下的创新应用”分论坛在东莞松山湖举行&#xff0c;区块链技术再次成为焦点。本次论坛以"区块链X"为主题&#xff0c;集结了行业专家、技术领袖、…