单例模式以及线程安全问题

news2024/11/17 13:31:30

单例模式的概念

单例模式是指的是整个系统生命周期内,保证一个类只能产生一个实例对象
保证类的唯一性 。
通过一些编码上的技巧,使编译器可以自动发现咱们的代码中是否有多个实例,并且在尝试创建多个实例的时候,直接编译出错。

应用场景:数据库连接池、多线程线程池 windows回收站

单例模式特点

单一实例:单例模式确保一个类只有一个实例。
全局访问点:单例模式提供了一个全局的访问点来获取这个唯一的实例。
延迟初始化:单例模式通常实现延迟初始化,即在实际需要实例之前不会创建实例,这样可以节省系统资源。
线程安全:在多线程环境中,单例模式需要确保即时在高并发的情况下也能保持实例的唯一性。

单例模式实现方式

单例模式有很多种实现方式,包括饿汉式和懒汉式 。
懒汉式会涉及到线程安全问题 可以使用加锁的方式可以解决线程安全。
而饿汉式就不会有线程安全问题
下面我们用代码来分别实现一下饿汉 和懒汉的单例模式:

package thread;
//单例模式  懒汉模式的实现

class SingletonLazy {
    private static  SingletonLazy instance = null;
    private static Object locker = new Object();

    public static SingletonLazy getInstance() {
      //  if (instance == null) {
            //synchronized (locker) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
           // }
       // }
        return instance;
    }
    public SingletonLazy() {}
}
public class Demo28 {

    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1 == s2);
    }
}

package thread;
//单例模式  懒汉模式的实现

class SingletonLazy {
    private static  SingletonLazy instance = null;
    private static Object locker = new Object();

    public static SingletonLazy getInstance() {
      //  if (instance == null) {
            //synchronized (locker) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
           // }
       // }
        return instance;
    }
    public SingletonLazy() {}
}
public class Demo28 {

    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1 == s2);
    }
}

单例模式多线程环境下的安全问题

在多线程中 饿汉式单例模式是线程安全的 而 懒汉式单例模式是线程不安全的
为什么呢 因为饿汉式写法 创建实例的时机是在Java线程启动(比main调用还早的时机)再后续线程执行获取对象的时候 意味着实例早就已经存在了 每个线程的获取操作就做了一件事 读取代码中静态变量的值
多个线程读取同一个变量的值 线程是安全的

懒汉式 则涉及到读和修改操作 就是要先判断instance里面的引用地址是否为空 为空才修改 多线程环境下可能就会产生bug
在这里插入图片描述
像上面图片这种执行顺序就会出现线程安全问题 就不止一个实例了 就不符合我们单例模式的初衷了。
那怎么办呢 我们最容易想到处理的方式就是加锁了 那要怎么加锁呢
比如这样加锁:
在这里插入图片描述
这样很明显还是线程不安全:
在这里插入图片描述
因为这个锁就相当于没加 两个线程还是会new两个对象 那怎么办呢 我们可不可以把if判断操作和new操作打包成一个原子 答案是当然可以。
在这里插入图片描述
像上面这样加锁 t1执行加锁之后 ,t2就会阻塞等待 直到t1释放锁(new完了)t2才拿到锁,才能进行条件判读 t2判断的时候instance早就非空了 ,也就不会再new了。
但是现在还存在一个问题 就是我们上面那种加锁虽然解决了线程安全问题 但是这样设计锁 每次调用那个getinstance方法,就需要先加锁,再执行后续操作。 但是懒汉模式只是一开始调用的时候存在线程安全问题 ,一旦实例创建好了,后续再调用就只是读取操作了 ,就不存在线程安全问题
但是我们这样加锁就会出现后面都没有线程安全问题了 但是我们还在加锁,这就有点画蛇添足了。因为锁本身也是有开销的可能会使线程阻塞。

那怎么办呢 我们可以引入双重if判定
在这里插入图片描述
在上面这种加锁方式下 首先我们要先判断一下是否需要加锁 实例化之后线程安全了就不用加锁了 实例化之前就应该加锁 在两个if判断之间,synchronized会使线程阻塞等待 阻塞过程其他线程会修改instance的值

下面我们来画个时间轴来解释:

在这里插入图片描述
当t2在进去第一个if条件之后就会阻塞等待 等到t1释放锁 现在instace已经不为null了 t2的第二个if条件也是进不去的 后面不为空了 锁就不用加了。
这样就解决了没有线程安全也加锁的情况了。

但是现在还有一个问题 就是内存可见性引起的线程安全问题 就相当于
t1线程修改了instance引用,t2有可能读不到(不过这种概率应该很小)为了避免这种情况的发生 我们还要加上volatile关键字
这个关键字还可以解决指令重排序问题

指令重排序

指令重排序也是编译器的一种优化策列 按照正常来说你写一段代码 cpu应该使按照顺序一条一条执行的 ,但是编译器就比较智能,会根据实际情况生成二进制指令的执行顺序,和你最初写的代码的顺序可能会存在差别
调整顺序最主要的目的就是为了提高效率,但是在保证逻辑是等价的。

在这里插入图片描述
上面执行这个代码会有三条指令
1、申请内存空间
2、调用构造方法(对内存空间进行初始化)
3、把此时内存空间的地址,赋值给instance引用

在多线程环境下 如果执行顺序是1 3 2 就会 出现线程安全问题
如果3指令比2指令先执行就会出现返回未初始化完毕的对象
就相当于t1线程执行完 instance就不是null了 但其实他是一个为初始化的对象 到时候t2线程执行的时候instance引用已经不是空的了 就进不去 就直接返回instance 了 返回了一个没有初始化完毕的对象 。这样就会导致很严重的线程安全问题 所以我们要加上volatile关键字 这样就很好的解决了指令重排序引起的线程安全问题。

总结

上面就是单例模式的相关实现和线程安全问题 当然单例模式还有很多延伸问题 怎么解决反射下能够保证是单例的模式 即使使用反射也不能破坏单例模式的唯一性呢 那可能就要用到枚举的实现 。但是我们上面讲的单例模式的内容 是经常用到的 在面试中也是会经常问到的 一般HR会让你现场写一个单例模式 你应该一步一步写 先不考虑线程安全问题 ,等着HR问你 你再慢慢一步一步加上解决的实现方法 。

谢谢大家的浏览 !!!

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

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

相关文章

哈佛大学商业评论 -- 第二篇:增强现实是如何工作的?

AR将全面融入公司发展战略! AR将成为人类和机器之间的新接口! AR将成为人类的关键技术之一! 请将此文转发给您的老板! --- 本文作者:Michael E.Porter和James E.Heppelmann 虽然物理世界是三维的,但大…

SpringCloud - 如何本地调试不会注册到线上环境(Nacos)?

问题描述 有时候我们需要本地调试注册到 Nacos 上,但是会影响线上服务的 Feign 请求打到本地导致不通影响了线上业务。 原因分析 一般最传统的解决方案就是修改本地 bootstrap.yml 的 spring.cloud.nacos.discovery.namespace spring:application:name: app-serv…

第十二篇【传奇开心果系列】Python自动化办公库技术点案例示例:深度解读Python自动化操作Word

传奇开心果系列博文 系列博文目录Python自动化办公库技术点案例示例系列 博文目录前言一、Python自动化操作Word介绍二、使用python-docx示例代码二、**使用win32com示例代码**三、使用comtypes示例代码四、使用docx-mailmerge示例代码五、基本操作示例代码六、高级操作示例代码…

mysql 磁盘空间100%

MySQL大事务可能会导致过多的占用临时文件,导致磁盘空间撑满的问题 本例说明下binlog cache产生的临时文件 案例复现 调小binlog_cache_size,让DML使用临时文件 使用存储过程模拟大事务 创建表 create table t1( id int AUTO_INCREMENT, name varchar…

文献阅读:将条形码神经解剖学与空间转录分析相结合,可以识别投射神经元相关基因

文献介绍 「文献题目」 Integrating barcoded neuroanatomy with spatial transcriptional profiling enables identification of gene correlates of projections 「研究团队」 Anthony M. Zador(美国冷泉港实验室) 「发表时间」 2021-05-10 「发表期…

【蓝桥杯-枚举模板题】

蓝桥杯-枚举模板题 滑雪课程设计 P3650新的家乡 P8587枚举子集 B3622Air Cownditioning P9011 滑雪课程设计 P3650 核心的思路是把数据规定在[i,i17]里&#xff0c;不够的补&#xff0c;过大的减。枚举i以求最少的钱。 #include<bits/stdc.h> using namespace std;int n…

编程生活day6--回文子串、蛇形填充数组、笨小猴、单词排序

回文子串 题目描述 给定一个字符串&#xff0c;输出所有长度至少为2的回文子串。 回文子串即从左往右输出和从右往左输出结果是一样的字符串&#xff0c;比如&#xff1a;abba&#xff0c;cccdeedccc都是回文字符串。 输入 一个字符串&#xff0c;由字母或数字组成。长度5…

剖析C++中的菱形继承

剖析C中的菱形继承 文章目录 剖析C中的菱形继承前言菱形继承虚拟继承与虚基表总结 前言 在面向对象编程中&#xff0c;继承允许我们构建出复杂的类关系和对象模型。然而&#xff0c;当多个类继承自同一个基类时&#xff0c;可能会引发结构上的冲突和数据冗余。这种情况在C中被…

基于深度学习的番茄新鲜度检测系统(网页版+YOLOv8/v7/v6/v5代码+训练数据集)

摘要&#xff1a;本文深入研究了基于YOLOv8/v7/v6/v5的番茄新鲜度检测系统&#xff0c;核心采用YOLOv8并整合了YOLOv7、YOLOv6、YOLOv5算法&#xff0c;进行性能指标对比&#xff1b;详述了国内外研究现状、数据集处理、算法原理、模型构建与训练代码&#xff0c;及基于Streaml…

Dockerfile详解构建镜像

Dockerfile构建企业级镜像 在服务器上可以通过源码或rpm方式部署Nginx服务&#xff0c;但不利于大规模的部署。为提高效率&#xff0c;可以通过Dockerfile的方式将Nginx服务封装到镜像中&#xff0c;然后Docker基于镜像快速启动容器&#xff0c;实现服务的快速部署。 Dockerf…

Python机器学习实验 Python 数据分析

1.实验目的 掌握常见数据预处理方法&#xff0c;熟练运用数据分析方法&#xff0c;并掌握 Python 中的 Numpy、 Pandas 模块提供的数据分析方法。 2.实验内容 1. Pandas 基本数据处理 使用 Pandas 模块&#xff0c;完成以下操作。 &#xff08;1&#xff09;创建一个由 0 到 50…

Stable Diffusion 本地化部署

一、前言 最近在家背八股文背诵得快吐了&#xff0c;烦闷的时候&#xff0c;看到使用 AI 进行作图&#xff0c;可以使用本地话部署。刚好自己家里的电脑&#xff0c;之前买来玩暗黑4&#xff0c;配置相对来说来可以&#xff0c;就拿来试试。 此篇是按照 Github 上的 stable-d…

【智能算法】磷虾群算法(KHA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2012年&#xff0c;Gandomi等人受到自然界中磷虾生存行为启发&#xff0c;提出了磷虾群算法&#xff08;Krill Herd Algorithm, KHA&#xff09;。 2.算法原理 2.1算法思想 KHA受南极鳞虾群觅食行…

软件测试用例(2)

具体的设计方法 -- 黑盒测试 因果图 因果图是一种简化的逻辑图, 能直观地表明程序的输入条件(原因)和输出动作(结果)之间的相互关系. 因果图法是借助图形来设计测试用例的一种系统方法, 特别适用于被测试程序具有多种输入条件, 程序的输出又依赖于输入条件的各种情况. 因果图…

【C++】C++11类的新功能

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 默认成员函数 类成…

2024年阿里云服务器购买、续费、升级优惠活动价格表

2024年阿里云服务器租用费用&#xff0c;云服务器ECS经济型e实例2核2G、3M固定带宽99元一年&#xff0c;轻量应用服务器2核2G3M带宽轻量服务器一年61元&#xff0c;ECS u1服务器2核4G5M固定带宽199元一年&#xff0c;2核4G4M带宽轻量服务器一年165元12个月&#xff0c;2核4G服务…

计算机网络——32差错检测和纠正

差错检测和纠正 错误检测 EDC 差错检测和纠错位&#xff08;冗余位&#xff09; D 数据由差错检测保护&#xff0c;可以包含头部字段 错误检测不是100%可靠的 协议会泄露一些错误&#xff0c;但是很少更长的EDC字段可以得到更好的检测和纠正效果 奇偶校验 单bit奇偶校验 …

【办公类-47-01】20240404 Word内部照片批量缩小长宽(课题资料系列)

作品展示 背景需求 最近在做《运用Python优化3-6岁幼儿学习操作材料的实践研究》的课题研究资料&#xff08;上半学期和下半学期&#xff09;。 将CSDN里面相关的研究照片文字贴入Word后&#xff0c;就发现一张图片就占了A4竖版一页&#xff0c;太大了。我想把word里面的所有…

vue2源码解析——vue中如何进行依赖收集、响应式原理

vue每个组件实例vm都有一个渲染watcher。每个响应式对象的属性key都有一个dep对象。所谓的依赖收集&#xff0c;就是让每个属性记住它依赖的watcher。但是属性可能用在多个模板里&#xff0c;所以&#xff0c;一个属性可能对应多个watcher。因此&#xff0c;在vue2中&#xff0…

Qt + VS2017 创建一个简单的图片加载应用程序

简介&#xff1a; 本文介绍了如何使用Qt创建一个简单的图片加载应用程序。该应用程序可以打开图片文件并在界面上显示选定的图片&#xff0c;并保存用户上次选择的图片路径。 1. 创建项目&#xff1a; 首先&#xff0c;在VS中创建一个新的Qt Widgets应用程序项目&#xff0c;并…