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

目录

一、单例模式概述

二、懒汉模式和饿汉模式

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新人,每位同志的支持都会给博主莫大的动力,如果有任何疑问,或者发现了任何错误,都欢迎大家在评论区交流“ψ(`∇´)ψ

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

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

相关文章

YOLOv5算法进阶改进(15)— 引入密集连接卷积网络DenseNet

前言:Hello大家好,我是小哥谈。DenseNet(密集连接卷积网络)是一种深度学习神经网络架构,它在2017年由Gao Huang等人提出。DenseNet的核心思想是通过密集连接(dense connection)来促进信息的流动和共享。在传统的卷积神经网络中,每个层的输入只来自于前一层的输出。而在…

Linux下编译EtherCAT主站SOEM-1.4.1

目录 1、SOEM下载 2、CMake安装​​​​​​ 3、编译 环境:Ubuntu1604. 1、SOEM下载 最新版为SOEM-v1.4.0,可以从github下载地址: https://github.com/OpenEtherCATsociety/SOEM 2、CMake安装​​​​​​ 3、编译 解压文件&#xff0c…

WebSocket学习笔记以及用户与客服聊天案例简单实现(springboot+vue)

一:介绍: 二:http协议与websocket对比: 三:websocket协议: 四:实现: 4.1客户端: 4.2服务端: 五:案例: 环境:做一个书店…

分布式任务调度框架XXL-JOB详解

分布式任务调度 概述 场景: 如12306网站根据不同车次设置放票时间点,商品成功发货后向客户发送短信提醒等任务,某财务系统需要在每天上午10天前统计前一天的账单数据 任务的调度是指系统为了完成特定业务,基于给定的时间点,时间间隔&#…

onnx转换为rknn置信度大于1,图像出现乱框问题解决

前言 环境介绍: 1.编译环境 Ubuntu 18.04.5 LTS 2.RKNN版本 py3.8-rknn2-1.4.0 3.单板 迅为itop-3568开发板 一、现象 采用yolov5训练并将pt转换为onnx,再将onnx采用py3.8-rknn2-1.4.0推理转换为rknn出现置信度大于1,并且图像乱框问题…

【服务器】RAID(独立磁盘冗余阵列)

RAID(独立磁盘冗余阵列) 一、RAID的介绍二、RAID的分类#2-1 RAID 02-2 RAID 1#2-3 RAID 32-4 RAID 52-5 RAID 62-6 RAID 10(先做镜像,再做条带化)2-7 RAID 01(先做条带,再做镜像)2-8 RAID比较 三、磁盘阵列…

代码随想录刷题第24天

今天正式进入回溯。看了看文章介绍,回溯并不是很高效的算法,本质上是穷举操作。代码形式较为固定。 第一题为组合问题,用树形结构模拟,利用回溯算法三部曲,确定终止条件与单层逻辑,写出如下代码。 不难发现…

负载均衡下webshell连接

目录 一、什么是负载均衡 分类 负载均衡算法 分类介绍 分类 均衡技术 主要应用 安装docker-compose 2.1上传的文件丢失 2.2 命令执行时的漂移 2.3 大工具投放失败 2.4 内网穿透工具失效 3.一些解决方案 总结 一、什么是负载均衡 负载均衡(Load Balanc…

网络安全挑战:威胁建模的应对策略与实践

在数字威胁不断演变的时代,了解和降低网络安全风险对各种规模的组织都至关重要。威胁建模作为安全领域的一个关键流程,提供了一种识别、评估和应对潜在安全威胁的结构化方法。本文将深入探讨威胁建模的复杂性,探索其机制、方法、实际应用、优…

python爬虫5

1.selenium交互 无页面浏览器速度更快 #配置好的自己不用管 from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionschrome_options Options()chrome_options.add_argument(‐‐headless)chrome_options.add_argument(‐‐disable‐gpu)# path…

109.乐理基础-五线谱-五线谱的附点、休止符、连线、延音线

内容参考于:三分钟音乐社 上一个内容:五线谱的拍号、音符与写法-CSDN博客 上一个内容里练习的答案: 附点:写在符头的右方,附点的作用与简谱一样,延长前面音符本身时值的一半(附点)…

Hadoop3.x基础(3)- Yarn

来源:B站尚硅谷 目录 Yarn资源调度器Yarn基础架构Yarn工作机制作业提交全过程Yarn调度器和调度算法先进先出调度器(FIFO)容量调度器(Capacity Scheduler)公平调度器(Fair Scheduler) Yarn常用命…

回归预测 | Matlab实现POA-CNN-LSTM-Attention鹈鹕算法优化卷积长短期记忆网络注意力多变量回归预测(SE注意力机制)

回归预测 | Matlab实现POA-CNN-LSTM-Attention鹈鹕算法优化卷积长短期记忆网络注意力多变量回归预测(SE注意力机制) 目录 回归预测 | Matlab实现POA-CNN-LSTM-Attention鹈鹕算法优化卷积长短期记忆网络注意力多变量回归预测(SE注意力机制&…

RocketMQ—RocketMQ发送同步、异步、单向、延迟、批量、顺序、批量消息、带标签消息

RocketMQ—RocketMQ发送同步、异步、单向、延迟、批量、顺序、批量消息、带标签消息 发送同步消息 生产者发送消息,mq进行确认,然后返回给生产者状态。这就是同步消息。 前文demo程序就是发送的同步消息。 发送异步消息 异步消息通常用在对响应时间敏…

gorm day1

gorm day1 gorm简介gorm声明模型 代码样例基本来自官方文档 Gorm简介 什么是ORM? 对象关系映射(Objection Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库(如mysql数据库)存在的互不匹配现象的计数。简单来说,ORM是通…

计算机毕设医院挂号预约系统ssm

项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: vue mybatis Maven mysql5.7或8.0等等组成,B…

【Redis】整理

对于现代大型系统而言,缓存是一个绕不开的技术话题,一提到缓存我们很容易想到Redis。 Redis整理,供回顾参考

单片机学习笔记---定时器/计数器(简述版!)

目录 定时器的介绍 定时计数器的定时原理 定时计数器的内部结构 两种控制寄存器 (1)工作方式寄存器TMOD (2)控制寄存器TCON 定时计数器的工作方式 方式0 方式1 方式2 方式3 定时器的配置步骤 第一步,对…

《幻兽帕鲁》好玩吗?幻兽帕鲁能在Mac上运行吗?

最近一款叫做《幻兽帕鲁》的新游戏走红,成为了Steam游戏平台上,连续3周的销量冠军,有不少Mac电脑用户,利用Crossover成功玩上了《幻兽帕鲁》,其实Crossover已经支持很多3A游戏,包括《赛博朋克2077》《博德之…

Nicn的刷题日常之字符串左旋(详细图解思路,多解法,建议三连收藏)

目录 1.题目描述 一 2.解题想法图解 2.1直接解 2.2巧解 3.题目描述二 3.1.1思路1 3.1.2 思路2 4.结语 1.题目描述 一 实现现一个函数,可以左旋字符串中的k个字符。 例如: ABCD左旋一个字符得到BCDA ABCD左旋两个字符得到CDAB 2.解题想法图解 2.…