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

【多线程案例】设计模式-单例模式

1.单例模式

什么是单例模式?

所谓单例,即单个实例。通过编码技巧约定某个类只能有唯一一个实例对象,并且提前在类里面创建好一个实例对象,把构造方法私有化,再对外提供获取这个实例对象的方法,(方法名通常是用getInstance这个名称)。 

根据创建时机不同,分为两种:

1.类加载的时候创建,也称为饿汉模式。

public class Singleton {//私有构造方法 禁止外界创建实例对象private Singleton() {};//唯一实例对象private static Singleton instance = new Singleton();//为外界提供获取唯一实例的方法public static Singleton getInstance() {return instance;}}

2.在第一次使用的时候创建,也称为懒汉模式  但这种有线程安全问题。

public class SingletonLazy {//私有构造方法private SingletonLazy() {}//实例对象private static SingletonLazy instance = null;//首次调用该方法时才真正创建出实例public static SingletonLazy getInstance() {if(instance == null) {instance = new SingletonLazy();}return instance;}}

总结:

高效性:饿汉模式是在类加载的时候就会创建实例,不管后面用不用得到,都会创建出来。而懒汉模式是只有你真正用了,才会创建出实例,如果不用则不创建,这样也就比较灵活,也就省下了创建实例这一开销。

比如有个非常大的文档(10G)需要打开,有两种方式打开:

  • 先把所有的内容都加载到内存中,然后再显示内容。即饿汉。
  • 先只加载一部分数据到内存,立即显示内容。随着用户翻页,再动态加载其他内容。即懒汉。

为什么懒汉模式会有线程安全问题?

先来说说线程安全问题产生的原因。

  • 如果多个线程同时修改同一个变量,就有可能出现线程安全问题。
  • 如果多个线程同时读取同一个变量,是不会出现线程安全问题的。

饿汉模式中是直接创建实例并返回实例,而懒汉模式是通过判断进行了修改,既读又修改。这种判断再修改就可能会导致线程不安全问题(因为可能会new多次,创建多个实例的话就不是单例模式了)。假设有两个线程 t1,t2,假如t1进行判断instance为null,准备new时,这时候可能会出现t1还没new呢,t2就开始判断instance是否为null。那此时instance肯定为null。这样的话,实例就会被创建多次。显然这就违背了单例模式的要求:单个实例。

如何使懒汉模式线程安全?

进行加锁。  

加锁也得注意咋加,要看加的合不合适,不是说加了就好了。

比如这种加锁:

public class SingletonLazy {//私有构造方法private SingletonLazy() {}//实例对象private static SingletonLazy instance = null;//首次调用该方法时才真正创建出实例public static SingletonLazy getInstance() {if(instance == null) {synchronized (SingletonLazy.class) {instance = new SingletonLazy();}}return instance;}}

缺点:没有使 if判断 和 new操作 成为一个整体,虽然在实例对象的时候加锁了,但是线程在if判断的时候,没有加锁,还是会出现误判。假设有两个线程t1,t2,由于if判断并没有加锁,两个线程是可以同时判断的,如果t2线程刚好在t1线程判断instance为nullt1线程进入new之前或还没new完时t2进行if判断,也是会创建多个实例对象的, 这就导致虽然new的时候加了锁线程是顺序执行的,但new外面的逻辑线程还是随机调度的。于是给整个if上锁。

public class SingletonLazy {//私有构造方法private SingletonLazy() {}//实例对象private static SingletonLazy instance = null;//首次调用该方法时才真正创建出实例public static SingletonLazy getInstance() {synchronized (SingletonLazy.class) {//if和new成为一个整体if(instance == null) {instance = new SingletonLazy();}}return instance;}}

这样加锁弥补了上一个代码的缺点,但是还有一个问题,加锁这种操作就是把调度的随机性改为顺序执行了,那效率,性能必然会大打折扣,况且我们把加锁放在最外面的话,只要用到实例都要加锁,而创建实例对象只有在首次时会发生线程不安全,其实加锁一次就行,用不着回回都进行加锁。这个代码线程虽然是安全了,但是同时效率也降低了,那么有没有一种既能使线程安全又能使效率比较快的代码逻辑呢?当然有,一种方法是在加锁外面再加一层if判断。即两层if。代码如下:

public class SingletonLazy {//私有构造方法private SingletonLazy() {}//实例对象private static SingletonLazy instance = null;//首次调用该方法时才真正创建出实例public static SingletonLazy getInstance() {if(instance == null) {synchronized (SingletonLazy.class) {if(instance == null) {instance = new SingletonLazy();}}}return instance;}}

外面这层if就是用来判断对象是否创建好,如果创建好了,就不用进入外层if加锁,直接执行return。代码效率一下提高。如果没创建好,才会进入并加锁。而不是想上面代码频繁加锁。里面的if是判断是否需要new对象。

但是上面代码还是会有一个问题,就是指令重排序问题。

指令重排序问题是什么?

说到底和内存可见性一样,都是编译器为了增加效率,而对原有代码的执行顺序做出调整。调整的前提是保持逻辑不变。

举个例子:假如我们去超市买东西,需要买菜,买衣服,买首饰,买玩具。此时若按照衣服,玩具,首饰,菜这种顺序效率是最高的。

在单例模式中,new操作,时可能会触发指令重排序问题的,new操作可分为三步:

  1. 申请内存空间。
  2. 在内空间上构造对象(构造方法)。
  3. 给对象引用。

其中1的顺序不变,2和3的顺序是可以换的。执行1->2->3顺序使我们希望的,但若是执行1->3->2顺序,执行到3时,对象虽然不是null了,但是此时的对象还没有初始化,贸然使用是非法的。若有两个线程,一个线程才执行到1->3,另一个线程去使用还未new完的这个对象,就会引发异常。

但虽然这样说,我们不是加了锁吗,那不应该是其他没加锁的线程阻塞等待加锁的这个线程执行完new的三步后,释放锁后,其他线程才能继续执行吗,为什么我加锁的线程还没new完,甚至是还没释放锁呢,其他线程就已经去使用对象了?原因是:另一个线程压根就没进入外层if。一个线程加锁,没拿到锁的等待,不是锁一个线程拿到锁了,不管其他线程在干嘛都得停下来等待,而是执行到有synchronized语句时,才等待。既然这个线程都没进入外层if,肯定碰不到synchronized语句,就会直接return,实例就被拿去使用了。

上述问题的核心是解决指令重排序问题,解决办法就是给实例对象加volatile修饰。

public class SingletonLazy {//私有构造方法private SingletonLazy() {}//实例对象private static volatile SingletonLazy instance = null;//首次调用该方法时才真正创建出实例public static SingletonLazy getInstance() {if(instance == null) {synchronized (SingletonLazy.class) {if(instance == null) {instance = new SingletonLazy();}}}return instance;}}

    

应用场景举例: 

  1. 外部资源:每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。内部资源:大多数软件都有一个(或多个)属性文件存放系统配置,这样的系统应该有一个对象管理这些属性文件 。
  2.  Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~ 
  3.  windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。 
  4. 网站的计数器,一般也是采用单例模式实现,否则难以同步。 
  5.  应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。 
  6. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。 
  7. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。 
  8. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。 
  9. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
  10. HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例。

相关文章:

【多线程案例】设计模式-单例模式

1.单例模式 什么是单例模式? 所谓单例,即单个实例。通过编码技巧约定某个类只能有唯一一个实例对象,并且提前在类里面创建好一个实例对象,把构造方法私有化,再对外提供获取这个实例对象的方法,&#xff0…...

MyBatis-Plus演绎:数据权限控制,优雅至极!

🎉🎉欢迎来到我的CSDN主页!🎉🎉 🏅我是尘缘,一个在CSDN分享笔记的博主。📚📚 👉点击这里,就可以查看我的主页啦!👇&#x…...

医学专题--多组学在药物治疗靶点筛选中的研究思路

研究背景 药物靶点(drug target):是指药物在体内的作用结合位点,包括基因位点、受体、酶、离子通路、核酸等生物大分子,靶向药物从单靶点药物到多靶点药物都在不断进行临床探索。小分子多靶点药物因其多靶向性、高效率…...

搜索与图论总结

算法博文DFSDFS(深度优先)-CSDN博客BFSBFS(宽度优先)(最短路)-CSDN博客有向图与无向图有向图与无向图(邻接表)_无向图有向图邻接表_人生导师yxc的博客-CSDN博客拓扑排序topsort(拓扑排序)-CSDN博客最短路最…...

lv8 嵌入式开发-网络编程开发 15I/O多路复用及select函数

目录 1 I/O多路复用 1.1 select函数及其他接口相关介绍 1.2 原TCP—socket示例: 1.3 实现select函数TCP—socket示例: 2 练习 1 I/O多路复用 多路复用的实现方式 1.1 select函数及其他接口相关介绍 int select(int nfds, fd_set *readfds, fd_set…...

阿里云 linux tomcat 无法访问方法

1、阿里云放行tomcat端口 例如7077端口号 2、linux 命令行防火墙 设置端口打开 以下命令查看是否开启指定端口 firewall-cmd --list-ports以下命令添加指定端口让防火墙放行 firewall-cmd --zonepublic --add-port3306/tcp --permanent以下命令重新启动防火墙 systemctl re…...

公园视频监控系统如何改造?人工智能又能提供哪些帮助?

近日合肥市骆岗公园宣布正式开园,作为目前世界最大的城市公园,占地12.7万平方公里,如此壮观宏伟的建设,也吸引到了不少市民进行参观打卡。不管大型小型,城市里的公园都是随处可见的,那么,公园安…...

面试算法19:最多删除一个字符得到回文

题目 给定一个字符串,请判断如果最多从字符串中删除一个字符能不能得到一个回文字符串。例如,如果输入字符串"abca",由于删除字符’b’或’c’就能得到一个回文字符串,因此输出为true。 分析 本题还是从字符串的两端…...

H5+Css3文本溢出添加省略号(包括插件)

一、单行 溢出隐藏 添加省略号 p{overflow: hidden;text-overflow:ellipsis;white-space: nowrap; }二、多行 溢出隐藏 省略号 p{display: -webkit-box;-webkit-box-orient: vertical;/*设置省略号在容器第四行文本后*/-webkit-line-clamp: 4; overflow: hidden; }局限性&…...

将休眠镜像文件hiberfil.sys移动到D盘,可以减少C盘好几个G的空间占用

hiberfil.sys是什么文件? 该文件是开启休眠功能后,系统自动生成的内存镜像文件,以便我们唤醒电脑之后可以快速开启程序。 1、首先打开电脑,使用“windowsR”组合键进入运行,输入“regedit”命令。 2、在注册表编辑器中…...

YTM32的模数转换器ADC外设模块详解

文章目录 简介原理与机制ADC转换器的上下电和省电模式ADC转换结果和FIFOADC转换队列的工作模式ADC转换器的触发信号ADC转换器的看门狗中断事件和DMA 应用要点(软件)总结参考文献 简介 YTM32的ADC转换器外设最多可以集成32个输入通道,最高12b…...

前端vue学习笔记——Vuex

1.概念 在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。 2.何时使用?…...

7个在Github上的flutter开源程序

阅读大量代码是提高开发技能的最佳方法之一。该开源项目是了解最佳实践、编码风格和许多其他主题的最佳场所。 软件开发最受欢迎的领域之一是跨平台移动应用程序开发。Flutter 是您可以使用的最流行的跨平台移动应用程序开发工具之一。今天,我们将了解 7 个开源 Flu…...

计算机基础

分值:3-7 1. 计算机系统概述 2. 计算机组成结构 3. 存储结构 3.1. 层次化存储结构 一般用什么调什么,局部性原理 内存和外存可以统称为虚拟存储器 我们可以操作哪些:操作外存、内存、CPU寄存器。Cache具有透明性。 3.2. Cache Cache的功…...

Oracle-ASM实例communication error问题处理

问题背景: Oracle数据库日志出现大量的WARNING: ASM communication error: op 0 state 0x0 (15055)错误 问题分析: 首先检查ASM实例的状态,尝试通过sqlplus / as sysasm连接asm实例,出现Connected to an idle instance连接asm实例失败 检查ASM实例的后台…...

gin路由相关方法

c.Request.URL.Path 拿到请求的路径 package mainimport ( "fmt" "github.com/gin-gonic/gin" "net/http")//路由重定向,请求转发,ANY ,NoRoute,路由组func main() { r : gin.Default() // -------…...

vue项目 Editor.md使用示例

简介 Editor.md 支持“标准” Markdown / CommonMark 和 Github 风格的语法,也可变身为代码编辑器; 支持实时预览、图片(跨域)上传、预格式文本/代码/表格插入、代码折叠、搜索替换、只读模式、自定义样式主题和多语言语法高亮等…...

12.3 实现模拟鼠标录制回放

本节将向读者介绍如何使用键盘鼠标操控模拟技术,键盘鼠标操控模拟技术是一种非常实用的技术,可以自动化执行一些重复性的任务,提高工作效率,在Windows系统下,通过使用各种键盘鼠标控制函数实现动态捕捉和模拟特定功能的…...

【计算机网络-自顶向下方法】应用层(SMTP、POP3、DNS)

目录 1. Electronic Mail电子邮件应用画像1.1 电子邮件系统1.2 邮件报文格式1.3 邮件访问 2. DNS(Domain Name System)2.1 DNS提供的服务2.2 DNS工作机理2.3 DNS资源记录2.4 DNS协议,报文2.5 小结 1. Electronic Mail 电子邮件应用画像 应用…...

【Pm4py第八讲】关于Statistics

本节用于介绍pm4py中的统计函数,包括统计轨迹变体、案例持续时间、案例到达时间等。 1.函数概述 本次主要介绍Pm4py中一些常见的统计函数,总览如下表: 函数名说明pm4py.stats.get_start_activities()从事件日志中获取开始活动。pm4py.stats.…...

【Python】 -- 趣味代码 - 小恐龙游戏

文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

剑指offer20_链表中环的入口节点

链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化

缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

提升移动端网页调试效率:WebDebugX 与常见工具组合实践

在日常移动端开发中,网页调试始终是一个高频但又极具挑战的环节。尤其在面对 iOS 与 Android 的混合技术栈、各种设备差异化行为时,开发者迫切需要一套高效、可靠且跨平台的调试方案。过去,我们或多或少使用过 Chrome DevTools、Remote Debug…...

Monorepo架构: Nx Cloud 扩展能力与缓存加速

借助 Nx Cloud 实现项目协同与加速构建 1 ) 缓存工作原理分析 在了解了本地缓存和远程缓存之后,我们来探究缓存是如何工作的。以计算文件的哈希串为例,若后续运行任务时文件哈希串未变,系统会直接使用对应的输出和制品文件。 2 …...

WebRTC调研

WebRTC是什么,为什么,如何使用 WebRTC有什么优势 WebRTC Architecture Amazon KVS WebRTC 其它厂商WebRTC 海康门禁WebRTC 海康门禁其他界面整理 威视通WebRTC 局域网 Google浏览器 Microsoft Edge 公网 RTSP RTMP NVR ONVIF SIP SRT WebRTC协…...

2025-05-08-deepseek本地化部署

title: 2025-05-08-deepseek 本地化部署 tags: 深度学习 程序开发 2025-05-08-deepseek 本地化部署 参考博客 本地部署 DeepSeek:小白也能轻松搞定! 如何给本地部署的 DeepSeek 投喂数据,让他更懂你 [实验目的]:理解系统架构与原…...

Springboot 高校报修与互助平台小程序

一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,高校报修与互助平台小程序被用户普遍使用,为…...

java 局域网 rtsp 取流 WebSocket 推送到前端显示 低延迟

众所周知 摄像头取流推流显示前端延迟大 传统方法是服务器取摄像头的rtsp流 然后客户端连服务器 中转多了,延迟一定不小。 假设相机没有专网 公网 1相机自带推流 直接推送到云服务器 然后客户端拉去 2相机只有rtsp ,边缘服务器拉流推送到云服务器 …...