单例模式以及线程安全问题
单例模式的概念
单例模式是指的是整个系统生命周期内,保证一个类只能产生一个实例对象
保证类的唯一性 。
通过一些编码上的技巧,使编译器可以自动发现咱们的代码中是否有多个实例,并且在尝试创建多个实例的时候,直接编译出错。
应用场景:数据库连接池、多线程线程池 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中完成编译和运行环境部署
大纲 解释型语言编译环境解释环境编译型语言编译环境运行环境 方法编译环境安装系统安装编译依赖下载代码特殊处理(可以忽略)编译准备(可以忽略)编译打包依赖(编译结果) 运行环境安装操作系统安装运行时依赖…...
利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...
Cursor实现用excel数据填充word模版的方法
cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
Neo4j 集群管理:原理、技术与最佳实践深度解析
Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...
站群服务器的应用场景都有哪些?
站群服务器主要是为了多个网站的托管和管理所设计的,可以通过集中管理和高效资源的分配,来支持多个独立的网站同时运行,让每一个网站都可以分配到独立的IP地址,避免出现IP关联的风险,用户还可以通过控制面板进行管理功…...
Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...
