单例模式:懒汉饿汉线程安全问题

        在我们前几篇文章中都了解了一些关于线程的知识,那么在多线程的情况下如何创建单例模式,其中的线程安全问题如何解决?


目录

1.什么是单例模式? (饿汉模式)

2.单例模式(懒汉模式)

*懒汉模式与懒汉模式的对比

*如何解决懒汉模式下线程不安全问题? 


1.什么是单例模式? (饿汉模式)

        单例模式:某个类,在进程中只有唯一的实例,不能new多次。例如如下代码:

class Singleton{private static Singleton singleton = new Singleton();public static Singleton getSingleton() {return singleton;}//将构造方法设置为private禁止外部重新创建private Singleton(){//空着就行}
}
public class ThreadDemo12 {public static void main(String[] args) {Singleton s1 = Singleton.getSingleton();Singleton s2 = Singleton.getSingleton();
//        外部不允许再new这个类的实例
//        Singleton s3 = new Singleton();}
}

        我们通过操作,来禁止外部在产生新的实例
        1.将成员变量Singleton设置成static.标志着次成员变量是类的静态成员.那自然只有一份.
        2.将类的构造方法设置成private.那外部就不能再创建其他的实例本体了.
        3.提供getter方法,只有调用getter方法才能获取唯一实例本体.

        我们可以发现s1,s2这两个实例是同一个,不信的玉粉可以打印一句:sout(s1==s2);看是否是true.但是s3就不可以了.这种单例模式我们称为"饿汉模式".意味着我们在新建成员变量的时候,就new实例,只是在调用getter方法的时候返回这个实例本体.下面我再介绍一种单例模式:"懒汉模式".

2.单例模式(懒汉模式)

        观察懒汉模式的例子: 

class SingletonLazy{private static SingletonLazy singleton = null;private SingletonLazy(){};//构造方法public static SingletonLazy getSingleton() {if(singleton == null){singleton = new SingletonLazy();}return singleton;}
}
public class ThreadDemo13 {public static void main(String[] args) {SingletonLazy s1 = SingletonLazy.getSingleton();SingletonLazy s2 = SingletonLazy.getSingleton();System.out.println(s1==s2);//不能执行新的new操作
//        SingletonLazy s3 = new SingletonLazy();}

        懒汉模式就意味着:我们在创建成员变量的时候,先不进行new操作,先让其=null;然后再调用getSingleton()方法的时候,去判断它是否为null,如果为null,代表着当前成员未被创建,就new;否则代表当前对象已经存在就返回这个对象即可.

*懒汉模式与懒汉模式的对比

观察上述两个案例代码思考一个问题:
在多线程模式在哪个模式有线程安全问题?懒or饿or都有or都没有?   why?

 

正确答案是:  饿汉模式没有线程安全问题,懒汉模式有线程安全问题!!! 

那么为什么会这样呢? 我们要注意,此处有个大前提:多线程下!!!在多线程环境下,我们饿汉模式突出的是"急迫""急需",而懒汉模式顾名思义就是"非必要不创建""从容".试想在线程快速调度的情况中,饿汉模式是有优势的:无论怎么调度,我上来就new,

private static Singleton singleton = new Singleton();

这一行代码是原子性的,无法拆分,这样就能保证我的对象只能被new一次.在对应的getSingleton()方法中也是"只读"操作,我们说过"只读"情况下是没有线程安全问题的而懒汉模式则是先不new,需要了再new,但是你new的时候有经过一系列的判断,新建new然后返回,万一在途中被切走了,那你new的对象就不止一个了.

*如何解决懒汉模式下线程不安全问题? 

那么我们需要寻找到一中解决办法:保证懒汉模式在多线程环境下线程安全问题:有以下三步:

  1. 为了保证原子性,需要加锁.
  2. 为了防止线程多次调度下创建多个对象,需要双重 if 判定.
  3. 为了禁止指令重排序,需要加上volatile 关键字. 

下面我们分别来解析其中的道理:

1.加锁

         我们可以直观的对比出来:懒汉模式与饿汉模式最大的区别就是:饿汉模式的new操作是原子性的.那么我们已经熟悉过可以打包代码的方法-----加锁,那么第一步就是给getSingleton()方法加锁,这里你既可以给方法前缀加上关键字synchronized,也可以在if 判断的时候加上synchronized,但是万万不可以这样加锁:

这样相当于没加,因为你要确保你的原子性是if 判定所包含的所有内容,所以稳妥的办法是在if 外面加锁:like that:

 

千万注意别把锁加错位置了!!! 还是不明白的玉粉可能你需要仔细研究一下"懒汉模式与饿汉模式的区别"......

2.双重if 判定. 

那么我们为什么需要双重if 判定呢?双重 if 怎么写呢?来看正确案例:

 

        有些玉粉可能就疑惑了:俩if 判定一模一样啊????为什么???  这里我要强调一下:不是if 长的一样就代表一个意思,也不是代码赘余了,这两个if 有不同的初心!!!在多线程的环境下,第一个if判断的是"是否要加锁",因为加锁操作实际上是非常低效的操作,加锁就可能有阻塞,如果没有第一个if判定,那么我们只要调用getSingleton()方法就会触发"锁竞争",是非常不友好的.第二个if判断的是,线程无论是否经历了调度,加锁后的singleton是否还是null.因为在两个if判定中间有加锁操作,加锁意味着有可能出现"锁竞争",有可能会发生"阻塞",等到真加上锁了,其中线程可能已经被切换了N次,那么这时候就有种"士别三日""如隔春秋"的感觉了,这时候的singleton是不是还未被其他线程创建就不得而知了,那就必须再次判定,如果"此singleton"还是"彼singleton"那就继续new吧,如果不是就直接返回singleton对象了.....

3.volatile关键字 

        这里是小玉一直不太懂的地方,现在终于懂了也希望和大家分享一下心得:在讲加锁操作关键字synchronized的时候,我们说synchronized能禁止指令重排序这个说法存疑!!!不然我们发明什么volatile干什么?volatile才是明确的1.用来保证内存可见性2.用来禁止指令重排序,但是在多线程创建对象的时候不存在什么"内存可见性"这一说,所以它再次的作用只是用来禁止指令重排序的.
        试想一下:在你创建对象new操作的时候,大致分为三步:1.申请内存. 2.调用构造方法初始化. 3.返回对象地址. 指令重排序可能会让new操作从正常的123变成132.如果执行顺序真的是132,那么1完成之后该3了,此时线程被调度走了,其他线程可能会以为该对象是完整的对象,那么在访问它的属性的时候,就会发现它其实是一个没有初始化的"空壳子",里面没有方法没有属性...什么都干不了......所以禁止指令重排序是必要操作,那么更改完的代码如下:

class SingletonLazy{volatile private static SingletonLazy singleton = null;private SingletonLazy(){};//构造方法public static SingletonLazy getSingleton() {if(singleton == null) {synchronized (SingletonLazy.class) {if (singleton == null) {singleton = new SingletonLazy();}}}return singleton;}
}
public class ThreadDemo13 {public static void main(String[] args) {SingletonLazy s1 = SingletonLazy.getSingleton();SingletonLazy s2 = SingletonLazy.getSingleton();System.out.println(s1==s2);//不能执行新的new操作
//        SingletonLazy s3 = new SingletonLazy();}}

如此就没有线程安全问题了...............


        好了小玉先讲这么多,其实小玉在这一篇想讲一下"阻塞队列""生产者消费者模型"的,因为看了b站 的一个视频印象很深刻,感觉很有东西可以讲,所以就文思泉涌想开始写,但是没有单例模式的铺垫很难讲好这些东西,所以就换成了将单例模式及懒汉&饿汉了.whatever,小玉下一章就可以将这些内容了,过年了小玉有些偷懒,最近心情也不是很好,有一个繁琐的事对心境造成了影响,写博客可以说是我的排解途径之一吧......期待小玉吧! 小玉会继续努力的!!!!!!!
        在此祝大家新年快乐,龙年小玉在实现自己的梦想,希望大家&玉粉也能梦想成真!!!

 

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

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

相关文章

【后端高频面试题--SpringBoot篇】

🚀 作者 :“码上有前” 🚀 文章简介 :后端高频面试题 🚀 欢迎小伙伴们 点赞👍、收藏⭐、留言💬 这里写目录标题 1.什么是SpringBoot?它的主要特点是什么?2.列举一些Spri…

《剑指 Offer》专项突破 - 面试题 43 : 在完全二叉树中添加节点(两种方法 + C++ 实现)

目录 前言 方法一 方法二 前言 题目链接:LCR 043. 完全二叉树插入器 - 力扣(LeetCode) 题目: 在完全二叉树中,除最后一层之外其他层的节点都是满的(第 n 层有 个节点)。最后一层的节点可能…

SQL,HQL刷题,尚硅谷

目录 相关表数据: 题目及思路解析: 汇总分析 1、查询编号为“02”的课程的总成绩 2、查询参加考试的学生个数 分组 1、查询各科成绩最高和最低的分,以如下的形式显示:课程号,最高分,最低分 2、查询每门课程…

springboot179基于javaweb的流浪宠物管理系统的设计与实现

简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的 适用于计算机类毕业设计,课程设计参考与学习用途。仅供学习参考, 不得用于商业或者非法用途,否则,一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **项…

鸿蒙开发第3篇__大数据量的列表加载性能优化

列表 是最常用到的组件 一 ForEach 渲染控制语法————Foreach Foreach的作用 遍历数组项,并创建相同的布局组件块在组件加载时, 将数组内容数据全部创建对应的组件内容, 渲染到页面上 const swiperImage: Resource[] {$r("app.me…

类与结构体(6)

我们上一起讲了这一期讲存储类和继承,这个难度很大的。 存储类 存储类主要规定了函数和变量的范围,在c中有这些存储类↓: ৹ auto(自动判断函数是什么类型) ৹ register (常用的变量和inline差不多,但应…

Netty应用——通过WebSocket编程实现服务器和客户端长连接(十八)

Http协议是无状态的,浏览器和服务器间的请求响应一次,下一次会重新创建连接要求:实现基于webSocket的长连接的全双工的交互改变Http协议多次请求的约束,实现长连接了, 服务器可以发送消息给浏览器客户端浏览器和服务器端会相互感知…

docker本地目录挂载

小命令 1、查看容器详情 docker inspect 容器名称 还是以nginx为例,上篇文章我们制作了nginx静态目录的数据卷,此时查看nginx容器时会展示出来(docker inspect nginx 展示信息太多,这里只截图数据卷挂载信息)&#…

【附代码】NumPy加速库NumExpr(大数据)

文章目录 相关文献测试电脑配置数组加减乘除数组乘方Pandas加减乘除总结 作者:小猪快跑 基础数学&计算数学,从事优化领域5年,主要研究方向:MIP求解器、整数规划、随机规划、智能优化算法 如有错误,欢迎指正。如有…

CVE-2022-0760 漏洞复现

CVE-2022-0760 NSS [HNCTF 2022 WEEK2]ohmywordpress 【CVE-2022-0760】 题目描述:flag在数据库里面。 开题: 顺着按钮一直点下去会发现出现一个按钮叫安装WordPress 安装完之后的界面,有一个搜索框。 F12看看network。 又出现了这个Wor…

MATLAB知识点:矩阵的除法

​讲解视频:可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇(数学建模清风主讲,适合零基础同学观看)_哔哩哔哩_bilibili 节选自第3章 3.4.2 算术运算 下面我们再来介绍矩阵的除法。事…

【C语言】实现双向链表

目录 (一)头文件 (二) 功能实现 (1)初始化 (2)打印链表 (3) 头插与头删 (4)尾插与尾删 (5)指定位置之后…

DMA直接内存访问,STM32实现高速数据传输使用配置

1、DMA运用场景 随着智能化、信息化的不断推进,嵌入式设备的数据处理量也呈现指数级增加,因此对于巨大的数据量处理的情况时,必须采取其它的方式去替CPU减负,以保证嵌入式设备性能。例如SD卡存储器和音视频、网络高速通信等其它情…

甘肃旅游服务平台:技术驱动的创新实践

✍✍计算机编程指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流! ⚡⚡ Java实战 |…

R语言阈值效应函数cut.tab2.0版发布(支持线性回归、逻辑回归、cox回归,自定义拐点)

阈值效应和饱和效应是剂量-反应关系中常见的两种现象。阈值效应是指当某种物质的剂量达到一定高度时,才会对生物体产生影响,而低于这个剂量则不会产生影响。饱和效应是指当某种物质的剂量达到一定高度后,其影响不再随剂量的增加而增加&#x…

【开源】基于JAVA+Vue+SpringBoot的假日旅社管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统介绍2.2 QA 问答 三、系统展示四、核心代码4.1 查询民宿4.2 新增民宿评论4.3 查询民宿新闻4.4 新建民宿预订单4.5 查询我的民宿预订单 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的假日旅社…

编写代码(LLVM的第一个项目)

下面这个完整代码 它相对较短,因为它建立在LLVM 流程的基础设施上 后者替我们完成大部分工作 我们从程序使用cl命名空间中的llvm工具(cl代表命令行)来实现我们的命令行接口 需要调用ParseCommandLineOption函数声明cl:&#xff…

【ES】--Elasticsearch的分词器详解

目录 一、前言二、分词器原理1、常用分词器2、ik分词器模式3、指定索引的某个字段进行分词测试3.1、采用ts_match_analyzer进行分词3.2、采用standard_analyzer进行分词三、如何调整分词器1、已存在的索引调整分词器2、特别的词语不能被拆开一、前言 最近项目需求,针对客户提…

[C#]winform制作圆形进度条好用的圆环圆形进度条控件和使用方法

【创建圆形进度条流程】 在C# WinForms应用程序中创建一个圆形进度条(通常用作仪表盘的显示)可以通过多种方式实现。下面是一个简单的例子,演示如何使用System.Drawing命名空间中的图形绘制功能来绘制一个基本的圆形进度条。 首先&#xff0…

在vscode上传项目到gitee

一、在Gitee上新建一个仓库 Tip:若已经创建过了,直接跳到第二部分看VsCode如何上传代码到Gitee 创建仓库比较简单,下面两张图就是整个过程,这里不在赘述,具体如下: 二、VsCode连接Gitee上创建的仓…