【设计模式】单例模式|饿汉模式|懒汉模式|指令重排序
目录
1.什么是单例模式?
2.如何保证单例?
3.两种写法
(1)饿汉模式(早创建)
(2)懒汉模式(缓执行,可能不执行)
4.应用场景
🔥5.多线程中的单例模式
(1)加锁
(2)双重if
(3)volatie
6.指令重排序(小概率出现问题)
(1)什么是指令重排序?
(2)以Instance = new SingletonLazy( );为例分析
(3)解决上述指令重排序问题
7.延伸:了解
🔥8.常见考察
1.什么是单例模式?
单例模式是最常见的设计模式之⼀
Q:啥是设计模式?
A:编程中典型场景的解决方案,设计模式好⽐象棋中的"棋谱".红⽅当头炮,⿊⽅⻢来跳.针对红⽅的⼀些⾛法,⿊⽅应招的时候有⼀些固定的套路.按照套路来⾛局势就不会吃亏
单例模式即某个类在进程中又能有唯一实例
- 有且只有一个对象,不会new出来多个对象,这样的对象就是“单例”
2.如何保证单例?
(1)保证单例:instance只有唯一一个,初始化也只是执行一次的
- 保证单例:instance只有唯一一个,初始化也只是执行一次的
static修饰的,其实是“类属性”,就是在“类对象”上的,每个类的类对象在JVM中只有一个,里面的静态成员,只有一份 - 后续需要使用这个类的实例,就可以直接通过getInstance来获取已经new好的这个,而不是重新new
(2) 核心操作:private:禁止外部代码来创建该类的实例
- 怎么操作?类之外的代码,尝试new的时候,势必就要调用构造方法,由于构造方法私有,无法调动,就会编译出错
3.两种写法
(1)饿汉模式(早创建)
唯一实例创建时机非常早,类似于饿了很久的人,看到了吃的就赶紧开始吃(急迫)
package thread;//单例,饿汉模式
//唯一实例创建时机非常早,类似于饿了很久的人,看到了吃的就赶紧开始吃(急迫)
class Singleton {private static Singleton instance = new Singleton();public static Singleton getInstance() {return instance;}private Singleton() {}
}
public class Demo27 {public static void main(String[] args) {Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s1 == s2);//Singleton s3 = new Singleton();}
}
(2)懒汉模式(缓执行,可能不执行)
懒是提高效率,节省开销的体现 啥时候调用,就啥时候创建,如果不调用,就不创建了
package thread;//单例模式,懒汉模式的实现
class SingletonLazy {private static SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null) {instance = new SingletonLazy();}return instance;}private SingletonLazy (){}
}public class Demo28 {public static void main(String[] args) {SingletonLazy s1 = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s1 == s2);//SingletonLazy s3 = new SingletonLazy();}
}
Q:如果有多个线程同时调用getInstance,是否会产生线程安全问题?
A:
- 饿汉模式不存在线程安全问题,而懒汉模式存在
- 饿汉模式:实例早就有了,每个线程getInstance,就是读取上面的静态变量,多个线程读取同一个变量,是线程安全的
- 懒汉模式:instance = new SingletonLazy,赋值操作就是修改,而且操作不是原子的,肯定就是线程不安全的
图解:懒汉模式:非原子性操作

- 还可能存在其他执行顺序,t2再次new,可能创建多个实例
- instance只是一个引用,instance中地址指向的那个对象可能就是一个大对象,上述代码会出现覆盖,第二个对象的地址覆盖了第一个,进一步第一个对象没有引用指向了,就会被GC回收(但是这个创建时间的开销,是客观存在的)
4.应用场景
(1)写的服务器,要从硬盘上加载100G的数据到内存(加载到若干个哈希表中),肯定要写一个类,封装上述加载操作,并且获取一些获取、处理数据的业务逻辑
- 代码中的有些对象,本身就不应该是多个实例的,从业务角度就应该是单个实例
一个实例就管理100G的内存数据 - 搞多个实例,就是N*100G的内存数据,机器肯定吃不消,没必要
(2)MySQL的配置文件,专门管理配置,需要加载配置数据到内存中提供其他代码使用,这样的类也是单例的
- 如果是多个实例,就存储了多份数据,如果一样还罢了,如果不一样,以哪个为准?
🔥5.多线程中的单例模式
懒汉模式的线程安全问题如何解决呢?
(1)加锁
Q:这样加锁线程就安全了吗?
public static SingletonLazy getInstance() {if (instance == null) {synchronized (locker) {instance = new SingletonLazy();}}return instance;}
A: 没有,t2拿到锁后,还在直接执行new操作
图解:

应该把if和new操作打包成一个原子操作
public static SingletonLazy getInstance() {synchronized (locker) {if(instance == null) {instance = new SingletonLazy();}}return instance;}
(2)双重if
加锁之后的问题Q:懒汉模式只有最开始调用getInstance会存在线程安全问题,一旦把实例创建好了,后续再调用,就是只读操作,就不存在线程安全问题了,但是上述代码,只要一调用getInstance方法,就需要先加锁,再执行后续操作(后续没有线程安全问题,还要加锁,有开销)
真正的解决A:再加一层判断
连续两次一样的条件判断(单线程中无意义,但是多线程含义就不一样)
第一层if:判定是否要加锁(new之前要加锁,new之后就不用加了)
第二层if:判断是否要创建对象
指令级理解

t2拿到锁,这个时候instance已经被t1修改了
(3)volatie
private static volatile SingletonLazy instance = null;
为了保证第一次线程修改,后续线程一定会读到,加上volatile【避免内存可见性+指令重排序问题】
综上:懒汉模式的线程安全版
//单例模式,懒汉模式的实现
class SingletonLazy {private static volatile 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;}private SingletonLazy (){}
}
6.指令重排序(小概率出现问题)
编译器的优化策略有很多:
把读内存优化到读寄存器,指令重排序,循环展开,条件分支预测...
(1)什么是指令重排序?
编译器会在保证逻辑是等价的情况下,调整二进制指令的执行顺序,从而提高效率
- 正常来说写的代码,最终会编译成为一系列的二进制指令,CPU会按照顺序,一条一条执行
(2)以Instance = new SingletonLazy( );为例分析
- 这行代码可以细分为三个步骤
- 申请内存空间
- 调用构造方法(对内存空间进行优化)
- 把此时内存空间的地址,赋值给Instance引用
- 在指令重排序的策略下,上述执行的过程,不一定是123,也可能是132(但是1一定先执行)
- 132这样的执行顺序,就可能存在线程安全问题
- 指令级理解

- 3一旦执行完,就意味着instance就非null,但是指向的对象其实是一个未初始化的对象(里面的成员都是0)
- 执行到t2的时候,instance已经非null了,这里的条件无法进行,直接返回未初始完毕的instance
- 后续如果t2中还有其他逻辑,就会对未初始完毕的对象进行操作,这样存在严重的问题
(3)解决上述指令重排序问题
- 加上volatile,主要是针对某个对象的读写过程中,不会出现重排序
- 很多地方都能重排序,但是只是针对这一过程中
- 这样t2线程读到的数据,一定是t1已经构造完毕的完整对象了(一定是123执行的对象)
7.延伸:了解
(1)单例模式要确保反射下安全,即使动用反射也无法破坏单例特性

(2)单例模式要确保序列化下安全,即使动用Java标准库的序列化机制,也无法破坏单例特性

- enum类型的实例天然支持序列化和反序列化
- 序列化:把对象转为二进制字符串
🔥8.常见考察
(1)为什么说饿汉式单例天生就是线程安全的?
实例早就有了,每个线程getInstance,就是读取上面的静态变量,多个线程读取同一个变量,是线程安全的
(2)传统的懒汉式单例为什么是非线程安全的?
instance = new SingletonLazy,赋值操作就是修改,而且操作不是原子的,肯定就是线程不安全的
(3)怎么修改传统的懒汉式单例,使其线程变得安全?
1.线程不安全的版本
2.加锁版本
3.加上双重if
4.最后加上volatile
(4)线程安全的单例的实现还有哪些,怎么实现?
静态内部类单例
public class StaticInnerClassSingleton {private StaticInnerClassSingleton() {}private static class SingletonHolder {private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();}public static StaticInnerClassSingleton getInstance() {return SingletonHolder.INSTANCE;} }静态内部类在外部类加载时不会被加载,只有在调用 getInstance 方法时才会加载类,从而创建实例。类加载过程是线程安全的,所以这种方式实现了线程安全的单例,并且具有延迟加载的特性
(5)双重检查模式、Volatile关键字在单例模式中的应用
1.双重检查模式:
第一层if:判定是否要加锁(new之前要加锁,new之后就不用加了)
第二层if:判断是否要创建对象
2.Volatile关键字:避免内存可见性+指令重排序问题
(6)ThreadLocal在单例模式中的应用
public class ThreadLocalSingleton {private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>() {@Overrideprotected ThreadLocalSingleton initialValue() {return new ThreadLocalSingleton();}};private ThreadLocalSingleton() {}public static ThreadLocalSingleton getInstance() {return threadLocalInstance.get();}
}
ThreadLocal 会为每个使用该实例的线程都提供一个独立的副本,每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。在单例模式中使用 ThreadLocal,可以保证每个线程都有自己的单例实例,适用于需要在每个线程中维护单例状态的场景
(7)枚举式单例
public enum EnumSingleton {INSTANCE;public void doSomething() {System.out.println("Doing something...");}
}
枚举式单例是实现单例模式的最佳方式之一。它是线程安全的,因为枚举类型的实例创建是由 JVM 保证线程安全的。而且枚举类型可以防止反序列化和反射攻击,因为 Java 规范中规定,枚举类型的 clone()、readObject()、readResolve() 等方法都不会破坏单例的唯一性。使用时可以直接通过 EnumSingleton.INSTANCE 来获取单例实例
相关文章:
【设计模式】单例模式|饿汉模式|懒汉模式|指令重排序
目录 1.什么是单例模式? 2.如何保证单例? 3.两种写法 (1)饿汉模式(早创建) (2)懒汉模式(缓执行,可能不执行) 4.应用场景 🔥5.多…...
Microsoft.Office.Interop.Excel 的简单操作
Microsoft.Office.Interop.Excel 的简单操作 1、安装 Microsoft.Office.Interop.Excel2、声明引用 Microsoft.Office.Interop.Excel3、简单的新建 EXCEL 操作代码4、将 DataGridView 表数据写到 EXCEL 操作代码5、将 EXCEL 表数据读取到 C# 数据表 DataTable 操作代码 1、安装 …...
说一下redis事务底层原理
Redis事务 1. 事务的基本流程 Redis 事务通过 MULTI、EXEC、WATCH 等命令实现,底层原理可以分为以下几个步骤: (1) MULTI 命令 当客户端发送 MULTI 命令时,Redis 会将客户端标记为“事务模式”。在事务模式下,客户端发送的所有…...
【powerjob】 powerjobserver注册服务IP错误
1、问题:powerjobserver 4.3.6 的服务器上有多个网卡对应多个ip,示例 eth0 :IP1 ,docker0:IP2 和worker 进行通信时 正确的应该时IP1 但是注册显示获取的确实IP2,导致 worker 通过ip2和server通信,网络不通,注册不上 2、解决方案 …...
01. HarmonyOS应用开发实践与技术解析
文章目录 前言项目概述HarmonyOS应用架构项目结构Ability生命周期 ArkTS语言特性装饰器状态管理 UI组件与布局基础组件响应式布局样式与主题 页面路由与参数传递页面跳转参数接收 数据绑定与循环渲染数据接口定义循环渲染 条件渲染组件生命周期最佳实践与性能优化组件复用响应式…...
【NLP 30、文本匹配任务 —— 传统机器学习算法】
目录 一、文本匹配任务的定义 1.狭义解释 2.广义解释 二、文本匹配的应用 1.问答对话 2.信息检索 3.文本匹配任务应用 三、智能问答 1.智能问答的基本思路 依照基础资源划分: 依照答案产出方式划分 依照NLP相关技术划分 四、智能问答的价值 1.智能客服 2.Faq知识库问…...
爬虫Incapsula reese84加密案例:Etihad航空
声明: 该文章为学习使用,严禁用于商业用途和非法用途,违者后果自负,由此产生的一切后果均与作者无关 一、找出需要加密的参数 1.js运行 atob(‘aHR0cHM6Ly93d3cuZXRpaGFkLmNvbS96aC1jbi8=’) 拿到网址,F12打开调试工具,随便搜索航班,切换到network搜索一个时间点可以找…...
【Vue教程】使用Vite快速搭建前端工程化项目 Vue3 Vite Node.js
??大家好!我是毛毛张! ??个人首页: ??今天毛毛张分享的是关于如何快速??♂搭建一个前端工程化的项目的环境搭建以及流程?? 文章目录 1.前端工程化环境搭建?? 1.1 什么是前端工程化1.2 nodejs的简介和安装 1.2.1 什么是Nodejs1.2.2 如何安装…...
如何将飞书多维表格与DeepSeek R1结合使用:效率提升的完美搭档
将飞书的多维表格与DeepSeek R1结合使用,就像为你的数据管理和分析之旅装上一台涡轮增压器。两者的合作,不仅仅在速度上让人耳目一新,更是将智能化分析带入了日常的工作场景。以下是它们如何相辅相成并改变我们工作方式的一些分享。 --- 在…...
算数操作符、赋值操作符、单目操作符、强制类型转换
一、算术操作符(、 -、 *、 /、 %) • - * / %操作符都是双⽬操作符,有**两个操作数**的符号就叫做双目操作符 10 4| || | 操作数1 操作数2// - % / * 以此类推•操作符也被叫做:运算符 1. 符号、符号 - 和 符号* •…...
为AI聊天工具添加一个知识系统 之133 详细设计之74通用编程语言 之4 架构及其核心
本篇继续讨论 通用编程语言。 说明:本阶段的所有讨论都是围绕这一主题展开的,但前面的讨论分成了三个大部分(后面列出了这一段的讨论题目的归属关系)-区别distinguish(各别): 文化和习俗。知识…...
RNN实现精神分裂症患者诊断(pytorch)
RNN理论知识 RNN(Recurrent Neural Network,循环神经网络) 是一种 专门用于处理序列数据(如时间序列、文本、语音、视频等)的神经网络。与普通的前馈神经网络(如 MLP、CNN)不同,RNN…...
私有云基础架构
基础配置 使用 VMWare Workstation 创建三台 2 CPU、8G内存、100 GB硬盘 的虚拟机 主机 IP 安装服务 web01 192.168.184.110 Apache、PHP database 192.168.184.111 MariaDB web02 192.168.184.112 Apache、PHP 由于 openEuler 22.09 系统已经停止维护了ÿ…...
rust学习笔记11-集合349. 两个数组的交集
rust除了结构体,还有集合类型,同样也很重要,常见的有数组(Array)、向量(Vector)、哈希表(HashMap) 和 集合(HashSet)字符串等,好意外呀…...
全栈(Java+vue)实习面试题(含答案)
在广州一个小公司(BOSS标注是0-20人,薪资2-3k),直接面试没有笔试,一开始就直接拿着简历问,也没有自我介绍,问题是结合场景题和八股文、基础。废话不多说,直接分享面试题目个大家做参考。 1、能…...
SQL经典常用查询语句
1. 基础查询语句 1.1 查询表中所有数据 在SQL中,查询表中所有数据是最基本的操作之一。通过使用SELECT * FROM table_name;语句,可以获取指定表中的所有记录和列。例如,假设有一个名为employees的表,包含员工的基本信息…...
超详细:数据库的基本架构
MySQL基础架构 下面这个图是我给出的一个MySQL基础架构图,可以清楚的了解到SQL语句在MySQL的各个模块进行执行过程。 然后MySQL可以分为两个部分,一个是server层,另一个是存储引擎。 server层 Server层涵盖了MySQL的大多数核心服务功能&am…...
AI催化新一轮创业潮与创富潮:深圳在抢跑
作者:尺度商业大掌柜黄利明 2025年春节伊始至今,从DeepSeek R1开源模型持续引发全球围观,到腾讯混元Turbo S模型发布秀出了"秒回"绝活,再到国务院发布《新一代人工智能发展规划(2025-2030)》重磅…...
Docker 深度解析:适合零基础用户的详解
此博客涵盖 Docker 的基本概念和作用、架构和核心组件、与传统虚拟机的对比、安装与基本操作,以及在实际开发和运维中的应用场景。 首先,详细解释了 Docker 的基本概念,包括它的诞生背景、作用及其如何解决传统应用部署中的问题。然后&#…...
SpringBoot生成唯一ID的方式
1.为什么要生成唯一ID? 数据唯一性:每个记录都需要有一个独一无二的标识符来确保数据的唯一性。这可以避免重复的数据行,并有助于准确地查询、更新或删除特定的记录。 数据完整性:通过使用唯一ID,可以保证数据库中的数…...
FastGPT 源码:RRF、Rerank 相关代码
文章目录 FastGPT 源码:RRF、Rerank 相关代码1. RRF (Reciprocal Rank Fusion) 合并实现2. Rerank 二次排序实现3. 重排序的主要特点4. 整个搜索流程5. 这种方式的优势 FastGPT 源码:RRF、Rerank 相关代码 下边介绍 RRF 合并和 Rerank 二次排序的相关实…...
Android视频流畅播放要素
要让 Android 设备流畅播放视频,需根据设备性能(低端、中端、高端)和播放场景(本地播放、在线流媒体)动态调整视频参数。以下是针对不同设备的推荐配置方案: 一、通用推荐配置(平衡兼容性与流畅…...
Python:类型转换和深浅拷贝,可变与不可变对象
int():转换为一个整数,只能转换由纯数字组成的字符串 浮点型强转整型会去掉小数点及后面的数,只保留整数部分 #如果字符串中有数字和正负号以外的字符就会报错 float():整形转换为浮点型会自动添加一位小数 .0 如果字符串中有…...
vcredist_x64 资源文件分享
vcredist_x64 是 Microsoft Visual C Redistributable 的 64 位版本,用于在 64 位 Windows 系统上运行使用 Visual C 开发的应用程序。它包含了运行这些应用程序所需的运行时组件。 vcredist_x64 资源工具网盘下载链接:https://pan.quark.cn/s/ef56f838f…...
Linux:vim快捷键
Linux打开vim默认第一个模式是:命令模式! 命令模式快捷键操作: gg:光标快速定位到最开始 shift g G:光标快速定位到最结尾 n shift g n G:光标快速定位到第n行 shift 6 ^:当前行开始 …...
DeepSeek在MATLAB上的部署与应用
在科技飞速发展的当下,人工智能与编程语言的融合不断拓展着创新边界。DeepSeek作为一款备受瞩目的大语言模型,其在自然语言处理领域展现出强大的能力。而MATLAB,作为科学计算和工程领域广泛应用的专业软件,拥有丰富的工具包和高效…...
NAT 代理服务 内网穿透
🌈 个人主页:Zfox_ 🔥 系列专栏:Linux 目录 一:🔥 NAT 技术背景二:🔥 NAT IP 转换过程三:🔥 NAPT四:🔥 代理服务器🦋 正向…...
高级课第五次作业
首先配置交换机,路由器 LSW1配置 [SW1]vlan batch 10 20 30 40 [SW1]int g0/0/2 [SW1-GigabitEthernet0/0/2]port link-type access [SW1-GigabitEthernet0/0/2]port default vlan 10 [SW1]int g0/0/3 [SW1-GigabitEthernet0/0/3]port link-type access […...
51单片机编程学习笔记——动态数码管显示多个数字
大纲 视觉残留原理生理基础神经传导与处理 应用与视觉暂留相关的现象 频闪融合不好的实现好的效果 延伸 在《51单片机编程学习笔记——动态数码管》一文中,我们看到如何使用动态数码管显示数字。但是基于动态数码管设计的特点,每次只能显示1个数字。这就…...
金蝶ERP星空对接流程
1.金蝶ERP星空OPENAPI地址: 金蝶云星空开放平台 2.下载金蝶云星空的对应SDK包 金蝶云星空开放平台 3.引入SDK流程步骤 引入Kingdee.CDP.WebApi.SDK 右键项目添加引用,在打开的引用管理器中选择浏览页签,点击浏览按钮,找到从官…...
