单例模式以及线程安全问题
单例模式的概念
单例模式是指的是整个系统生命周期内,保证一个类只能产生一个实例对象
保证类的唯一性 。
通过一些编码上的技巧,使编译器可以自动发现咱们的代码中是否有多个实例,并且在尝试创建多个实例的时候,直接编译出错。
应用场景:数据库连接池、多线程线程池 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问你 你再慢慢一步一步加上解决的实现方法 。
谢谢大家的浏览 !!!
相关文章:
单例模式以及线程安全问题
单例模式的概念 单例模式是指的是整个系统生命周期内,保证一个类只能产生一个实例对象 保证类的唯一性 。 通过一些编码上的技巧,使编译器可以自动发现咱们的代码中是否有多个实例,并且在尝试创建多个实例的时候,直接编译出错。 …...
车载电子电器架构 —— 软件下载
车载电子电器架构 —— 软件下载 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无…...
阿里云弹性计算通用算力型u1实例性能评测,性价比高
阿里云服务器u1是通用算力型云服务器,CPU采用2.5 GHz主频的Intel(R) Xeon(R) Platinum处理器,ECS通用算力型u1云服务器不适用于游戏和高频交易等需要极致性能的应用场景及对业务性能一致性有强诉求的应用场景(比如业务HA场景主备机需要性能一致)…...
Jupyter IPython帮助文档及其魔法命令
1.IPython 的帮助文档 使用 help() 使用 ? 使用 ?? tab 自动补全 shift tab 查看参数和函数说明 2.运行外部 Python 文件 使用下面命令运行外部 Python 文件(默认是当前目录,也可以使用绝对路径) %run *.py …...
设计模式总结-面向对象设计原则
面向对象设计原则 面向对象设计原则简介单一职责原则单一职责原则定义单一职责原则分析单一职责原则实例 开闭原则开闭原则定义开闭原则分析开闭原则实例 里氏代换原则里氏代换原则定义里氏代换原则分析 依赖倒转原则依赖倒转原则定义依赖倒转原则分析依赖倒转原则实例 接口隔离…...
绿联 安装zfile,创建属于自己的网盘,支持直链分享
绿联 安装zfile,创建属于自己的网盘,支持直链分享 1、镜像 zhaojun1998/zfile:latest ZFile ZFile 是一个适用于个人的在线网盘(列目录)程序,可以将你各个存储类型的存储源,统一到一个网页中查看、预览、维护,再也不用…...
KnowLog:基于知识增强的日志预训练语言模型|顶会ICSE 2024论文
徐波 东华大学副教授 东华大学计算机学院信息技术系副系主任,复旦大学知识工场实验室副主任,智能运维方向负责人。入选“上海市青年科技英才扬帆计划”。研究成果发表在IJCAI、ICDE、ICSE、ISSRE、ICWS、CIKM、COLING等国际会议上,曾获中国数…...
前端:用Sass简化媒体查询
在进行媒体查询的编写的时候,我们可以利用scss与与编译器,通过include混入的方式对代码进行简化,从而大大提高了代码的可维护性,也减少了代码的编写量,废话不多说,直接上代码 // 定义设备数值 $breakpoints…...
如何快速写出漂亮的Button按钮呢?
你是否曾在浏览网页时,被那些色彩鲜艳、功能多样的按钮所吸引?无论是提交表单,还是触发一个动作,按钮都扮演着不可或缺的角色。今天聊聊网页设计中的 <button> 标签。 1. 基础语法 什么是 <button> 标签 <butto…...
美摄科技AI智能图像矫正解决方案
图像已经成为了企业传播信息、展示产品的重要媒介,在日常拍摄过程中,由于摄影技巧的限制和拍摄环境的复杂多变,许多企业面临着图像内容倾斜、构图效果不佳等挑战,这无疑给企业的形象展示和信息传递带来了不小的困扰。 美摄科技深…...
上位机图像处理和嵌入式模块部署(qmacvisual查找圆缺角)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 前面我们讲过识别,讲过标定,讲过测量,讲过匹配,但就是没有讨论过基于图像的产品检测。但事实上&…...
Python 之 Fastapi 框架学习
依赖安装 Fastapi 有版本要求,需要的 Python 版本至少是 Python 3.8(不要犟,按照版本要求来,我最先也是在我 Python3.6 上装的,果不其然跑不起来),幸好我 Win7 老古董能支持的 Python 最高版本…...
C++初阶:stack和queue使用及模拟实现
stack的介绍和使用 stack的介绍 堆栈 - C 参考 (cplusplus.com) 翻译 : 1. stack 是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。 2. stack 是作为容器适配器被实现的,容器…...
LINUX系统CFS调度模型实现思考和仿真
关于LINUX资源调度 计算机系统中,管理资源的方式一般有两种方法,分别是时间分割和空间分割,可以通过分割硬件的相似性,让软件以一致的逻辑执行,CPU运行特点是在时刻点A和时刻B运行机制是一样的,不同的只是…...
兑换码生成算法
兑换码生成算法 兑换码生成算法1.兑换码的需求2.算法分析2.重兑校验算法3.防刷校验算法 3.算法实现 兑换码生成算法 兑换码生成通常涉及在特定场景下为用户提供特定产品或服务的权益或礼品,典型的应用场景包括优惠券、礼品卡、会员权益等。 1.兑换码的需求 要求如…...
Vue框架介绍简介
Vue.js,通常简称为Vue,是一个用于构建用户界面的渐进式框架。它发布于2014年2月,由Evan You设计并开发。Vue被设计为可以自底向上逐层应用,这使得开发者可以根据项目的需求灵活地使用Vue。无论是构建简单的轻量级应用,…...
的C++奇迹之旅:值和引用的本质效率与性能比较
文章目录 请添加图片描述 [TOC](文章目录) 📝引用# 🌠引用概念**引用**不是新定义一个变量,而是给**已存在变量取了一个别名**,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。>定义&#…...
【C++】vector问题解决(非法的间接寻址,迭代器失效 , memcpy拷贝问题)
送给大家一句话: 世界在旋转,我们跌跌撞撞前进,这就够了 —— 阿贝尔 加缪 vector问题解决 1 前言2 迭代器区间拷贝3 迭代器失效问题4 memcpy拷贝问题 1 前言 我们之前实现了手搓vector,但是当时依然有些问题没有解决ÿ…...
风控系统之普通规则条件,使用LiteFlow实现
个人博客:无奈何杨(wnhyang) 个人语雀:wnhyang 共享语雀:在线知识共享 Github:wnhyang - Overview 提要 参考:智能风控筑基手册:全面了解风控决策引擎 前面有可配置输入参数的接…...
在一套Dockerfile中完成编译和运行环境部署
大纲 解释型语言编译环境解释环境编译型语言编译环境运行环境 方法编译环境安装系统安装编译依赖下载代码特殊处理(可以忽略)编译准备(可以忽略)编译打包依赖(编译结果) 运行环境安装操作系统安装运行时依赖…...
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...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...
DBAPI如何优雅的获取单条数据
API如何优雅的获取单条数据 案例一 对于查询类API,查询的是单条数据,比如根据主键ID查询用户信息,sql如下: select id, name, age from user where id #{id}API默认返回的数据格式是多条的,如下: {&qu…...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...
【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...
