【多线程案例】设计模式-单例模式
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->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;}}
应用场景举例:
- 外部资源:每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。内部资源:大多数软件都有一个(或多个)属性文件存放系统配置,这样的系统应该有一个对象管理这些属性文件 。
- Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
- windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
- 网站的计数器,一般也是采用单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
- 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
- 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
- HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例。
相关文章:
【多线程案例】设计模式-单例模式
1.单例模式 什么是单例模式? 所谓单例,即单个实例。通过编码技巧约定某个类只能有唯一一个实例对象,并且提前在类里面创建好一个实例对象,把构造方法私有化,再对外提供获取这个实例对象的方法,࿰…...
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.…...
设计模式和设计原则回顾
设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...
