<设计模式>单例模式懒汉和饿汉
目录
一、单例模式概述
二、懒汉模式和饿汉模式
1.饿汉模式
1.1代码实现
1.2实现细节
1.3模式优劣
2.懒汉模式
2.1代码实现
2.2实现细节
2.3模式优劣
三、多线程下的线程安全问题
1.懒汉和饿汉线程安全问题分析
1.1安全的饿汉模式
1.2不安全的懒汉模式
2.懒汉线程安全实现
2.1代码实现
2.2实现细节
一、单例模式概述
单例模式是一种创建型模式,它的目的是确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。单例模式通常用于需要频繁创建和销毁同一对象的场景,通过单例模式可以减少系统性能开销,提高系统性能。
基础的单例模式分为两种,懒汉模式和饿汉模式。
例如对于相当大的对象(假设其管理10G的数据),使用一次创建一次或是过多创建该对象都会造成较大的系统性能开销,那我们能不能规定整个这个类只能创建出一个实例,即每次创建实例返回的都是同一个对象,这样不但避免了使用一次就创建一次的性能开销,也能避免创建多个对象对空间资源的浪费。
可以结合下例进一步理解:
对于一个“自助调料区”对象,在火锅店中需要他的“客人”线程可以在任何时间任何地区,同时同地的前来操作“自助调料区”对象,获取需要的内容。
在这一场景下,多个“自助调料区”对象无疑是没有必要的,正是单例模式大显身手的地方,采用了单例模式的火锅店就像是下达了“禁止多设调料区,需要调料都到这一个调料区”的指令,避免了资源的浪费。
说了这么多,单例模式怎么使用,又是怎么实现的呢?
且看下文。
二、懒汉模式和饿汉模式
1.饿汉模式
饿汉模式是单例模式的一种简单实现,‘式如其名’,饿汉模式主打的就是一个饿死鬼投胎,即类加载阶段就已经把实例创建出来了,相当于程序已启动就有这个实例了,总之非常迫切的感觉。
在饿汉模式中,类加载的时候就已经实例化对象,即“饿汉”在类加载时就完成了初始化,因此可以保证只有一个实例存在。
1.1代码实现
虽然Java标准库没法直接规定类所能创建实例的数量,但我们依然可以通过一些方法限制实例的创建,间接达到只能创建一个实例的效果。
饿汉模式的代码具体实现如下:
class Singleton {//实例为static修饰的类属性,类加载阶段创建private static Singleton instance = new Singleton();//通过getInstance方法获取唯一实例public static Singleton getInstance() {return instance;}//私有构造方法,无法通过new创建该类实例private Singleton() {};
}
1.2实现细节
第二行代码“private static Singleton instance = new Singleton();”
instance变量是Singleton类创建的唯一实例,分别被‘private’关键字和‘static’关键字修饰。private保证了该变量为类所私有,外界无法直接访问和修改,只能通过下面的getInstance方法获取该实例。static表示类属性,即instance作为Singleton类的属性在类加载阶段就被创建出来,且具有唯一性。
第三~五行代码“getInstance()”
作为获取唯一实例的唯一方法存在,需要由public和static修饰,使外界可以通过类直接调用该方法。
第六行代码“private Singleton() {};”
private关键字使Singleton类的构造方法私有,这样外界就没法new该类了。
1.3模式优劣
这种方式实现简单,但会导致类加载时就创建对象,如果不需要使用该对象则会造成资源浪费。同时,由于实例化对象在类加载时完成,因此无法在运行时改变实例状态。
2.懒汉模式
不同于饿汉模式的急不可耐,懒汉模式采用的是摆烂策略,就像博主暑假在家一样,不喊我我就绝不出门,妥妥的宅男。
在懒汉模式中,类加载的时候不会实例化对象,而是在第一次调用getInstance方法时才实例化对象。
2.1代码实现
class SingletonLazy {private static SingletonLazy instance = null;//通过getInstance方法获取唯一实例public static SingletonLazy getInstance() {if(instance == null){instance = new SingletonLazy();}return instance;}//私有构造方法,无法通过new创建该类实例private SingletonLazy() {};
}
2.2实现细节
上述实现和饿汉模式的实现差别不大,只不过并没有在类加载阶段直接创建实例,而是在第一次调用getIntance方法时才创建出实例,即调用getIntance且instance=null未初始化时。
2.3模式优劣
只有在需要时才创建对象,节省了系统资源,只是实现上要比饿汉模式要更加复杂。由于在多线程环境下可能导致多个线程同时实例化对象,因此需要加锁来保证线程安全。
三、多线程下的线程安全问题
上述懒汉模式和饿汉模式的实现针对的是单线程情况的代码,多线程下代码实现是否会出现问题还需要具体分析。
1.懒汉和饿汉线程安全问题分析
1.1安全的饿汉模式
我们知道,产生线程安全的原因可能是内存可见性、锁竞争、优化策略和线程调度策略,及其它。具体产生问题的原因可能是多个线程对同一空间读写操作产生的。
再看饿汉模式的代码实现
很显然Singleton类中唯一的可调用方法getIntance只涉及到读操作,并不会产生线程安全问题。而由于不涉及到锁,更不会因为锁竞争陷入死锁。所以,饿汉模式是线程安全的。
1.2不安全的懒汉模式
再看懒汉模式,getIntance方法中不仅涉及了读操作同时也涉及了写操作,这就为线程安全问题的产生埋下了隐患。
由于线程调度的随机性,当两个线程在同一时间调用该方法时,错落的执行顺序可能导致if语句出现不可避免的错判,进而导致最终创建了两个SingletonLazy实例,如下图:
①T1线程执行完if语句,因为第一次调用getIntance方法,intance==null,所以T1线程接下来将要创建SingletonLazy实例,并将其赋值给intance。
②轮到T2线程执行,由于T1线程中尚未进行实例创建,此时仍旧是instance==null,所以if语句判断通过。接下来创建实例、赋值一气呵成,最后还将创建的Singleton对象返回。
③再次轮到T1线程,继续执行,创建了一个和T2线程不同的Singleton实例,引用赋给instance。最后,这个引用又被返回。
综上所述,在多线程情况下竟然出现了两个懒汉实例,这不符合单例模式下一个类只能创建一个实例的原则,很可能产生无法预估的错误,妥妥的bug代码。所以单线程下实现的懒汉模式不是线程安全的。
2.懒汉线程安全实现
上文分析懒汉模式代码线程不安全的原因是进程的随即调度问题,这一点我们可以通过引入锁来保证代码的原子性(一个整体)。同时,还要注意其他线程安全问题。
2.1代码实现
class SingletonLazy {//锁对象private static Object lock = new Object();//唯一实例,新增的volatile关键字是为了禁止指令重排序导致bugprivate static volatile SingletonLazy instance = null;public static SingletonLazy getInstance() {//当instance不为空时不进行加锁,提高代码效率if(instance == null) {//通过锁保证创建实例代码的原子性,不会因为线程的随即调度而产生多个实例synchronized(lock) {//多线程情况下可能由于锁竞争陷入阻塞,所以其他线程可能创建过实例了if(instance == null){//虽然new SingletonLazy可以分解为三个指令,// 但instance受volatile保护不会指令重排序instance = new SingletonLazy();}}}return instance;}private SingletonLazy() {};
}
2.2实现细节
通过锁保证代码块的原子性,进而克服系统随机调度的问题:
①T1线程执行。实例未创建,if判断通过。
②轮到T2线程。实例未创建,if判断通过。T2线程首先获取到锁资源,synchronized代码块执行完毕后才释放锁。if判断通过,实例创建成功后,锁释放。
③锁释放,T1线程解除阻塞,获取到锁资源。由于T2线程已经创建实例成功,if判断不通过,不创建新的实例,解锁,返回instance。
④轮到T2线程,返回instance的值。
上述过程返回的是同一个实例,成功克服系统随机调度的问题。
通过额外的if嵌套提高代码效率:
对于单线程代码来说,两个完全一样的if判断就是在脱裤子放屁,多此一举。但对于多线程代码来说,由于线程的随机调度,线程阻塞等问题,紧邻的两行代码执行时间相隔的可能是海枯石烂。
就我们的代码来说,外层if的作用是在实例已经创建的情况下,如果再调用本方法,只需经过该if语句就可以直接返回值,结束方法。像较于加锁解锁,if作为跳转语句效率相对非常之高,可以提高代码的运行效率。
而内层if则是判断线程阻塞时其他线程没有创建实例,确保只创建出一个实例。
通过volatile关键字保障创建操作不会因为代码优化(指令重排序)产生问题:
指令重排序是因为编译器会在保持“代码逻辑不发生变化”这一前提下对我们的代码进行优化,举个形象的例子:
1.去楼下超市买菜
2.回家
3.下楼倒垃圾
假如我们的代码执行逻辑为1->2->3,代码优化过后可能执行逻辑就变为1->3->2,两种执行逻辑效果相同,但效率却大大提高了。
而在多线程代码中,代码优化却可能会导致bug的出现。例如当线程频繁对同一个变量进行读值,在代码优化过后可能就不会再从主内存中读值,而是直接从线程的寄存器中读值,这时如果修改主内存的值,线程是感知不到的,从而导致线程安全问题的出现。
new Singleton()可以分解为以下三个指令:
1.申请一段内存空间
2.在这个内存上调用构造方法,创造出这个实例
3.把这个内存地址赋值给instance引用变量
指令重排序会在保持“代码逻辑不发生变化”这一前提下对我们的代码进行优化。对于逻辑而言,上述三个指令的顺序123和132都是没有区别的,因此执行顺序可能被优化成132。
程序执行是一条指令一条指令执行的,因此三条指令执行过程中线程可能就会被调度走了,如下:
①执行完毕后instance不为空,但引用指向的空间还未初始化,因为指令2还未执行。
②因为instance不为空,外层if判断未通过,返回未初始化空间的引用instance,方法结束。因为实列未初始化,而初始化的时间又无法确定(随机调度,T1要和其他线程竞争),这时候使用这个实例就可能产生问题。
③执行时间不确定,可能产生问题。
想要避免上述情况的出现,就必须保证指令的执行顺序保持不变为123,想达到这一效果可以使用volatile关键字修饰instance,利用其禁止指令重排序的特性。
博主是Java新人,每位同志的支持都会给博主莫大的动力,如果有任何疑问,或者发现了任何错误,都欢迎大家在评论区交流“ψ(`∇´)ψ
相关文章:

<设计模式>单例模式懒汉和饿汉
目录 一、单例模式概述 二、懒汉模式和饿汉模式 1.饿汉模式 1.1代码实现 1.2实现细节 1.3模式优劣 2.懒汉模式 2.1代码实现 2.2实现细节 2.3模式优劣 三、多线程下的线程安全问题 1.懒汉和饿汉线程安全问题分析 1.1安全的饿汉模式 1.2不安全的懒汉模式 2.懒汉线程…...

二分查找------蓝桥杯
题目描述: 请实现无重复数字的升序数组的二分查找 给定一个元素升序的、无重复数字的整型数组 nums 和一个目标值 target,写一个函数搜索 nums 中的target,如果目标值存在返回下标 (下标从0 开始),否则返回-1 数据范围: 0 < l…...

今日arXiv最热NLP大模型论文:微软提出SliceGPT,删除25%模型参数,性能几乎无损
引言:探索大型语言模型的高效压缩方法 随着大型语言模型(LLMs)在自然语言处理领域的广泛应用,它们对计算和内存资源的巨大需求成为了一个不容忽视的问题。为了缓解这些资源限制,研究者们提出了多种模型压缩方法&#…...
ChatGPT实战100例 - (13) 写一个属于自己的 ChatGPT 新版 WebUI
文章目录 ChatGPT实战100例 - (13) 写一个属于自己的 ChatGPT 新版 WebUI一、ChatGPT(OpenAI)的新版API调用1.1 环境变量配置与调用1.2 新版api调用1.3 命令行流式输出二、Gradio制作自己的聊天WebUI2.1 流式WebUI2.2 样式调整三、总结参考ChatGPT实战100例 - (13) 写一个属于自…...

【计算机学院寒假社会实践】——服务走进社区,共绘幸福蓝图
为深入贯彻落实志愿者服务精神,扎实推进志愿者服务质量,2024年1月28日,曲阜师范大学计算机学院“青年扎根基层,服务走进社区”社会实践队队员周兴睿在孙宇老师的指导下,来到山东省滨州市陈集街道社区开展了为期一天的“…...

[python] 过年燃放烟花
目录 新年祝福语 一、作品展示 二、作品所用资源 三、代码与资源说明 四、代码库 五、完整代码 六、总结 新年祝福语 岁月总是悄然流转,让人感叹时间的飞逝,转眼间又快到了中国传统的新年(龙年)。 回首过去…...

数据结构与算法:图论(邻接表板子+BFS宽搜、DFS深搜+拓扑排序板子+最小生成树MST的Prim算法、Kruskal算法、Dijkstra算法)
前言 图的难点主要在于图的表达形式非常多,即数据结构实现的形式很多。算法本身不是很难理解。所以建议精通一种数据结构后遇到相关题写个转换数据结构的接口,再套自己的板子。 邻接表板子(图的定义和生成) public class Graph…...

Python之PySpark简单应用
文章目录 一、介绍1.准备工作2. 创建SparkSession对象:3. 读取数据:4. 数据处理与分析:5. 停止SparkSession: 二、示例1.读取解析csv数据2.解析计算序列数据map\flatmap 三、问题总结1.代码问题2.配置问题 一、介绍 PySpark是Apa…...
降维(Dimensionality Reduction)
一、动机一:数据压缩 这节我将开始谈论第二种类型的无监督学习问题,称为降维。有几个原因使我们可能想要做降维,其一是数据压缩,它不仅允许我们压缩数据使用较少的计算机内存或磁盘空间,而且它可以加快我们的学习算法。…...
web应用(网页)怎样调用浏览器插件(如metamask小狐狸钱包)
下边是与gpt的对话,代码可以在浏览器控制台验证 一,在网页上点击一个连接按钮 然后小狐狸钱包就打开了,是怎么实现的呢 当你在网页上点击一个连接按钮,然后自动打开MetaMask(通常被称为“小狐狸钱包”,一种…...

2024美赛数学建模C题完整论文教学(含十几个处理后数据表格及python代码)
大家好呀,从发布赛题一直到现在,总算完成了数学建模美赛本次C题目Momentum in Tennis完整的成品论文。 本论文可以保证原创,保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文。 C论文共49页&…...

Matplotlib绘制炫酷柱状图的艺术与技巧【第60篇—python:Matplotlib绘制柱状图】
文章目录 Matplotlib绘制炫酷柱状图的艺术与技巧1. 簇状柱状图2. 堆积柱状图3. 横向柱状图4. 百分比柱状图5. 3D柱状图6. 堆积横向柱状图7. 多系列百分比柱状图8. 3D堆积柱状图9. 带有误差线的柱状图10. 分组百分比柱状图11. 水平堆积柱状图12. 多面板柱状图13. 自定义颜色和样…...
window 挂载linux 网盘
背景:因为很多情况下,作为开发人员,我们都希望用Linux的编译环境,但是可以用windows下各种IDE来写code; linux 服务器安装NFS服务 说明:NFS 服务就是让不同的计算机可以在不同的操作系统之间共享文件,采用的就是服务端/客户端的架构,在NFS服务器上将目录设置为输出目录(…...

windows10忘记密码的解决方案
大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…...

进程和线程的区别详解
🎥 个人主页:Dikz12📕格言:那些在暗处执拗生长的花,终有一日会馥郁传香欢迎大家👍点赞✍评论⭐收藏 目录 进程 进程在系统中是如何管理的 进一步认识PCB 线程 能否一直增加线程数目来提高效率 进程和线程…...

(基于xml配置Aop)学习Spring的第十五天
一 . Spring Aop编程简介 再详细点 , 如下 二 . 基于xml配置Aop 解决proxy相关问题 解决问题开始用xml配置AOP 导入pom坐标 <dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.6</vers…...
Centos7环境安装PHP8
一、安装必要的模块 yum install -y bzip2-devel libcurl-devel libxml2-devel sqlite-devel oniguruma oniguruma-devel libxml2 libxml2-devel bzip2 bzip2-devel libcurl libcurl-devel libjpeg libjpeg-devel zstd libzstd-devel curl libcurl-devel libpng libpng-devel …...

No matching client found for package name ‘com.unity3d.player‘
2024年2月5日更新 必须使用Unity方式接入Unity项目!一句话解决所有问题。(真的别玩Android方式) 大致这问题出现原因是我在Unity采用了Android方式接入Firebase,而Android接入实际上和Unity接入方式有配置上的不一样,我…...

JavaWeb之HTML-CSS --黑马笔记
什么是HTML ? 标记语言:由标签构成的语言。 注意:HTML标签都是预定义好的,HTML代码直接在浏览器中运行,HTML标签由浏览器解析。 什么是CSS ? 开发工具 VS Code --安装文档和安装包都在网盘中 链接:https://p…...

logback日志配置
springboot默认使用logback 无需额外添加pom依赖 1.指定日志文件路径 当前项目路径 testlog文件夹下 linux会在项目jar包同级目录 <property name"log.path" value"./testlog" /> 如果是下面这样配置的话 window会保存在当前项目所在盘的home文件夹…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止
<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...

selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...