当前位置: 首页 > news >正文

<设计模式>单例模式懒汉和饿汉

目录

一、单例模式概述

二、懒汉模式和饿汉模式

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.懒汉线程…...

二分查找------蓝桥杯

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

今日arXiv最热NLP大模型论文:微软提出SliceGPT,删除25%模型参数,性能几乎无损

引言&#xff1a;探索大型语言模型的高效压缩方法 随着大型语言模型&#xff08;LLMs&#xff09;在自然语言处理领域的广泛应用&#xff0c;它们对计算和内存资源的巨大需求成为了一个不容忽视的问题。为了缓解这些资源限制&#xff0c;研究者们提出了多种模型压缩方法&#…...

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) 写一个属于自…...

【计算机学院寒假社会实践】——服务走进社区,共绘幸福蓝图

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

[python] 过年燃放烟花

目录 新年祝福语 一、作品展示 二、作品所用资源 三、代码与资源说明 四、代码库 五、完整代码 六、总结 新年祝福语 岁月总是悄然流转&#xff0c;让人感叹时间的飞逝&#xff0c;转眼间又快到了中国传统的新年&#xff08;龙年&#xff09;。 回首过去&#xf…...

数据结构与算法:图论(邻接表板子+BFS宽搜、DFS深搜+拓扑排序板子+最小生成树MST的Prim算法、Kruskal算法、Dijkstra算法)

前言 图的难点主要在于图的表达形式非常多&#xff0c;即数据结构实现的形式很多。算法本身不是很难理解。所以建议精通一种数据结构后遇到相关题写个转换数据结构的接口&#xff0c;再套自己的板子。 邻接表板子&#xff08;图的定义和生成&#xff09; public class Graph…...

Python之PySpark简单应用

文章目录 一、介绍1.准备工作2. 创建SparkSession对象&#xff1a;3. 读取数据&#xff1a;4. 数据处理与分析&#xff1a;5. 停止SparkSession&#xff1a; 二、示例1.读取解析csv数据2.解析计算序列数据map\flatmap 三、问题总结1.代码问题2.配置问题 一、介绍 PySpark是Apa…...

降维(Dimensionality Reduction)

一、动机一&#xff1a;数据压缩 这节我将开始谈论第二种类型的无监督学习问题&#xff0c;称为降维。有几个原因使我们可能想要做降维&#xff0c;其一是数据压缩&#xff0c;它不仅允许我们压缩数据使用较少的计算机内存或磁盘空间&#xff0c;而且它可以加快我们的学习算法。…...

web应用(网页)怎样调用浏览器插件(如metamask小狐狸钱包)

下边是与gpt的对话&#xff0c;代码可以在浏览器控制台验证 一&#xff0c;在网页上点击一个连接按钮 然后小狐狸钱包就打开了&#xff0c;是怎么实现的呢 当你在网页上点击一个连接按钮&#xff0c;然后自动打开MetaMask&#xff08;通常被称为“小狐狸钱包”&#xff0c;一种…...

2024美赛数学建模C题完整论文教学(含十几个处理后数据表格及python代码)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了数学建模美赛本次C题目Momentum in Tennis完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文。 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博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…...

进程和线程的区别详解

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f4d5;格言&#xff1a;那些在暗处执拗生长的花&#xff0c;终有一日会馥郁传香欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 进程 进程在系统中是如何管理的 进一步认识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项目&#xff01;一句话解决所有问题。&#xff08;真的别玩Android方式&#xff09; 大致这问题出现原因是我在Unity采用了Android方式接入Firebase&#xff0c;而Android接入实际上和Unity接入方式有配置上的不一样&#xff0c;我…...

JavaWeb之HTML-CSS --黑马笔记

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

logback日志配置

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

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序

一、开发环境准备 ​​工具安装​​&#xff1a; 下载安装DevEco Studio 4.0&#xff08;支持HarmonyOS 5&#xff09;配置HarmonyOS SDK 5.0确保Node.js版本≥14 ​​项目初始化​​&#xff1a; ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

GruntJS-前端自动化任务运行器从入门到实战

Grunt 完全指南&#xff1a;从入门到实战 一、Grunt 是什么&#xff1f; Grunt是一个基于 Node.js 的前端自动化任务运行器&#xff0c;主要用于自动化执行项目开发中重复性高的任务&#xff0c;例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案

引言 在分布式系统的事务处理中&#xff0c;如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议&#xff08;2PC&#xff09;通过准备阶段与提交阶段的协调机制&#xff0c;以同步决策模式确保事务原子性。其改进版本三阶段提交协议&#xff08;3PC&#xf…...

基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)

引言 在嵌入式系统中&#xff0c;用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例&#xff0c;介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单&#xff0c;执行相应操作&#xff0c;并提供平滑的滚动动画效果。 本文设计了一个…...

leetcode73-矩阵置零

leetcode 73 思路 记录 0 元素的位置&#xff1a;遍历整个矩阵&#xff0c;找出所有值为 0 的元素&#xff0c;并将它们的坐标记录在数组zeroPosition中置零操作&#xff1a;遍历记录的所有 0 元素位置&#xff0c;将每个位置对应的行和列的所有元素置为 0 具体步骤 初始化…...

shell脚本质数判断

shell脚本质数判断 shell输入一个正整数,判断是否为质数(素数&#xff09;shell求1-100内的质数shell求给定数组输出其中的质数 shell输入一个正整数,判断是否为质数(素数&#xff09; 思路&#xff1a; 1:1 2:1 2 3:1 2 3 4:1 2 3 4 5:1 2 3 4 5-------> 3:2 4:2 3 5:2 3…...