[单例模式]
目录
[设计模式]
单例模式
1. 饿汉模式
2. 懒汉模式
3. 单例模式的线程安全问题
[设计模式]
设计模式是软件工程中的一种常见做法, 它可以理解为"模板", 是针对一些常见的特定场景, 给出的一些比较好的固定的解决方案.
不同语言适用的设计模式是不一样的. 这里我们接下来要谈到的是java中典型的设计模式. 而且由于设计模式比较适合有一定编程经验之后, 再去详细学习, 所以我们本篇文章就只讨论几个经典的java设计模式
-
单例模式
在实际开发中, 某个进程中, 我们不希望某个类有多个实例对象, 希望它有且仅有一个实例对象而且不能再创建出来. --> 这个时候我们就可以使用单例模式这样的设计模式. 单例模式有两种写法, 一种叫饿汉模式, 一种叫懒汉模式. 下面我们就详细讨论一下这两种单例模式的写法.
1. 饿汉模式
"饿"的意思是"迫切的", 放到代码中意思就是需要我们在类被加载的时候就创建出这个单例的实例.
class Singleton {private static Singleton instance = new Singleton();public static Singleton getInstance() { //获取Singleton的实例对象, 但是每次获取的都是相同的对象instance.return instance;}private Singleton() {} //单例模式中最关键的部分: 将构造方法设置为私有. 防止在类外再创建出其他实例对象.
}
public class Demo24 {public static void main(String[] args) {Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();//两次获取到的对象应该是同一个对象. 我们可以在下面验证一下 (== 比较的是两个对象在内存中的地址,也就是它们是否指向同一个实例对象)System.out.println(s1 == s2);}
}
我们可以看到, 饿汉模式中,
(1) static修饰instance, 说明这里的instance是类成员(一个类只有一份, 随着类的加载而创建出来)
(2) static修饰的类方法 getInstance() 每次返回的都是同一个对象 instance.
(3) 将SIngleton类的构造方法设置为私有, 这就保证了在类外无法通过构造方法再创建出新的对象.
我们通过在main方法中创建两个引用s1和s2, 看到s1和s2指向的是同一个对象. 这也就代表了Singleton这个类只有一个实例.
[注]: 上述单例模式只能避免程序员失误, 调用了Singleton的构造方法创建新对象; 而无法避免程序员故意破坏单例模式(比如, 我们可以通过反射的方式拿到构造方法).
2. 懒汉模式
我们先通过一个形象的例子来理解饿汉和懒汉的区别: 比如我们现在有一个编辑器, 要打开一个非常大的文本文档. (1) 饿汉: 一启动, 就把所有的文本内容全都读取到内存中, 然后显示到界面. (2) 懒汉: 先只加载出一部分数据, 随着用户的翻页操作, 再按需加载剩下的内容. 根据上述表述, 我们可以确定, 懒汉模式一定比饿汉模式加载出来的速度更快, 用户的体验也就会更好. 所以, 我们日常开发中, 很多地方都青睐于使用懒汉模式.
class SingletonLazy {private static SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null) {instance = new SingletonLazy();}return instance;// 如果instance为空,则创建一个实例对象; 如果instance不为空,则直接返回instance}private SingletonLazy() {}
}public class Demo25 {public static void main(String[] args) {SingletonLazy s1 = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s1 == s2);}
}
如上述代码, 当我们首次调用getInstance时, 由于此时对象还没有创建, instance这个引用为空, 所以就会进入if分支, 创建出SingletonLazy对象. 后续如果再重复调用getInstance, 结果都不会再创建新的实例, 而是直接返回instancomen
我们还是通过在main方法中创建两个引用s1和s2, 看到s1和s2指向的是同一个对象. 这也就代表了SingletonLazy这个类只有一个实例.
3. 单例模式的线程安全问题
知道了单例模式的两种写法之后, 我们现在要判断: 这两种写法是否存在线程安全问题呢? (在多线程环境下, 多个线程调用getInstance, 是否会出现问题?)
(1) 饿汉模式:
我们可以看到, 饿汉模式的getInstance方法只涉及读操作, 并没有涉及任何写操作. 而我们在多个线程同时修改同一个变量时, 才容易出现线程安全问题. 所以饿汉模式是线程安全的.
(2) 懒汉模式:
- 原子操作问题
像这种 先条件判定, 再修改 的操作, 其实是典型的线程不安全代码.
比如, 我们现在有两个线程同时调用getInstance方法. 线程t1先执行if判断, 判断出instance为空. 此时t2线程插入进来了, 执行t2线程的if判断, 那么t2的判断结果同样是空, t2就会执行new SingletonLazy() 创建出一个新的对象. 而再切换回t1线程, 由于t1对instance的判断也为空, 所以, t1也会执行new SingletonLazy() 创建出一个新的对象. 那么这样的话, Singletonlazy类就被实例化了两次. 而单例模式要求类只能被实例化一次.
([注]: 虽然说后面创建的实例覆盖了前面创建的实例, 前面创建的实例没有引用变量引用的话很块回被销毁回收, 但是创建实例对象这个过程本身的开销就很大(比如有的类一个实例就要100个G), 所以我们仍然认为这个代码是有bug的)
所以, 为了解决上述线程不安全问题, 我们就需要进行"加锁"操作. 将条件判断和创建操作作为一个整体加上锁, 这样一来, if判断和new创建操作这个整体就成了一个"原子"操作. 这就保证了某个线程在顺序执行这两个操作的时候不会有别的线程插入进来.
class SingletonLazy {private static SingletonLazy instance = null;public static Object locker = new Object(); public static SingletonLazy getInstance() {synchronized(locker) {if (instance == null) {instance = new SingletonLazy();}return instance;// 如果instance为空,则创建一个实例对象; 如果instance不为空,则直接返回instance}}private SingletonLazy() {}
}
试想一下, 上述代码, 如果已经完成了创建对象的操作之后, 后续如果再调用getInstance, 就再也不会进入if分支中去了, 都是简单的读操作(return instance). 那么只有读操作的话, 不加锁也是线程安全的. 我们知道, 加锁这个操作, 对程序性能的影响还是挺大的. 所以, 我们只需要在第一次执行这个方法的时候(没有创建出对象的时候)加锁即可, 其他时候再执行这个方法, 都是线程安全的, 不需要加锁.
那么, 如何判断当前是不是第一次调用这个方法呢? --> 看是否已经创建出了实例对象, 如果还没有instance对象, 那就是第一次调用, 需要对里面的判断-创建对象 操作 加锁; 如果已经有了instance对象, 那就不是第一次调用, 就不需要加锁, 直接返回instance.
依据上述思考, 我们对代码做出如下修改:
public static SingletonLazy getInstance() {if (instance == null) { //外层if: 判断是否应该加锁synchronized(locker) {if (instance == null) { //内层if: 判断是否要创建对象instance = new SingletonLazy();}// 如果instance为空,则创建一个实例对象; 如果instance不为空,则直接返回instance}}return instance;}
注意: 这里, 外层if和内层if虽然条件恰好是一样的, 但是作用是完全不同的. 外层的if作用是: 判断是否要加锁. 内层if的作用是: 判断是否要创建新的对象.
- 指令重排序问题
编译器在执行创建对象的代码时, 为了提高性能, 可能会进行"指令重排序"操作.
instance = new SingletonLazy();
编译器在执行这个创建对象代码的时候, 会经过如下步骤: (1) 分配内存空间. (2) 执行构造方法. (3) 将对象的内存空间地址赋给引用变量. 正常来说, 是按照(1) -> (2) -> (3) 的顺序执行的. 但是编译器为了优化性能, 也可能按照(1) -> (3) -> (2) 的顺序执行.
public static SingletonLazy getInstance() {if (instance == null) { //外层if: 判断是否应该加锁synchronized(locker) {if (instance == null) { //内层if: 判断是否要创建对象instance = new SingletonLazy();}// 如果instance为空,则创建一个实例对象; 如果instance不为空,则直接返回instance}}return instance;}
那么, 我们试想, 如果在执行完(1) -> (3) 之后, 此时有别的线程切入, 执行if (instance == null) 判定, 那么此时判定instance就不为空了, 因为语句指向了内存空间(即使这个内存空间里什么都没有). 判定完instance不为空之后, 就会直接return instance. 那么如果这个线程拿到instance之后, 如果再调用里面的某个方法. 那么此时就会出现错误!!! (因为instance指向的内存空间是未初始化的).
那么如何解决这个情况呢? --> volatile. 我们可以在instance前面加上一个volatile修饰, 告诉系统, instance这个引用是"易变的, 易失的". 那么此时系统就会放弃对new SingletonLazy() 这个创建对象操作的优化, 按照(1) -> (2) -> (3) 的顺序执行创建对象操作.这样的话, 就不会出现上述问题了~
加上volatile的代码最终如下:
class SingletonLazy {private static volatile SingletonLazy instance = null;public static Object locker = new Object();public static SingletonLazy getInstance() {if (instance == null) { //外层if: 判断是否应该加锁synchronized(locker) {if (instance == null) { //内层if: 判断是否要创建对象instance = new SingletonLazy();}// 如果instance为空,则创建一个实例对象; 如果instance不为空,则直接返回instance}}return instance;}private SingletonLazy() {}
}
那么这样一个单例模式的代码无论在执行效率还是在线程安全上就都没有任何问题了.
好了, 本篇文章就介绍到这里啦, 大家如果有疑问欢迎评论, 如果喜欢小编的文章, 记得点赞收藏~~
相关文章:

[单例模式]
目录 [设计模式] 单例模式 1. 饿汉模式 2. 懒汉模式 3. 单例模式的线程安全问题 [设计模式] 设计模式是软件工程中的一种常见做法, 它可以理解为"模板", 是针对一些常见的特定场景, 给出的一些比较好的固定的解决方案. 不同语言适用的设计模式是不一样的. 这里…...
速盾:游戏盾的功能和原理详解
速盾有一款专注于网络游戏安全的防护系统,它通过实时监测游戏网络流量和玩家行为,以及使用先进的算法和技术进行分析和识别,检测出各种外挂、作弊行为和恶意攻击,从而保障游戏的公平性和玩家的安全性。 速盾游戏盾的主要功能包括…...
Spleeter:音频分离的革命性工具
目录 什么是Spleeter?Spleeter的工作原理Spleeter的应用场景Spleeter的技术优势Spleeter的挑战与局限性结论 什么是Spleeter? Spleeter 是一个由 Deezer 开发的开源音频源分离工具。它基于深度学习技术,尤其是卷积神经网络(CNN&a…...

【笔记】自动驾驶预测与决策规划_Part6_不确定性感知的决策过程
文章目录 0. 前言1. 部分观测的马尔可夫决策过程1.1 POMDP的思想以及与MDP的联系1.1.1 MDP的过程回顾1.1.2 POMDP定义1.1.3 与MDP的联系及区别POMDP 视角MDP 视角决策次数对最优解的影响 1.2 POMDP的3种常规解法1.2.1 连续状态的“Belief MDP”方法1. 信念状态的定义2. Belief …...
openresty入门教程:access_by_lua_block
在OpenResty中,access_by_lua_block 是一个功能强大的指令,它允许你在Nginx的访问控制阶段执行Lua脚本。这个阶段发生在Nginx处理请求的过程中,紧接在rewrite阶段之后,但在请求被传递到后端服务器(如PHP、Node.js等&am…...
Caused by: org.apache.flink.api.common.io.ParseException: Row too short:
Flink版本 1.17.2 错误描述 Caused by: org.apache.flink.api.common.io.ParseException: Row too short: 通过flink中的flinkSql直接使用对应的connector去获取csv文件内容,报获取的数据太短了 可能原因 1.创建的表字段多于csv文件当中的表头 定位 在获取csv…...
hbase的安装与简单操作
好的,这里是关于 HBase 的安装和基本操作的详细步骤,分成几个更清晰的阶段: 第一部分:安装和配置 HBase 1. 环境准备 HBase 依赖于 Hadoop,因此首先确保 Hadoop 已经正确安装和配置。如果没有安装,请先下…...

PySpark本地开发环境搭建
一.前置事项 请注意,需要先实现Windows的本地JDK和Hadoop的安装。 二.windows安装Anaconda 资源:Miniconda3-py38-4.11.0-Windows-x86-64,在window使用的Anaconda资源-CSDN文库 右键以管理员身份运行,选择你的安装路径&#x…...

【进阶】Stable Diffusion 插件 Controlnet 安装使用教程(图像精准控制)
Stable Diffusion WebUI 的绘画插件 Controlnet 最近更新了 V1.1 版本,发布了 14 个优化模型,并新增了多个预处理器,让它的功能比之前更加好用了,最近几天又连续更新了 3 个新 Reference 预处理器,可以直接根据图像生产…...
调试、发布自己的 npm 包
查看 npm 的配置 npm config ls登录 whoami 查看当前登录的用户 npm whoamiaduser 登录 adduser 有以下参数: –scope 作用域–registry 注册地址 默认地址:https://registry.npmjs.org/,也可通过.npmrc文件配置 npm login 是 …...
拓扑学与DNA双螺旋结构的奇妙连接:从算法到分子模拟
拓扑的形变指的是通过连续地拉伸、弯曲或扭曲物体而不进行撕裂或粘合来改变其形状的一种数学变换。拓扑形变属于拓扑学的一个分支,研究在这些操作下保持不变的性质。简单来说,它关注的是物体“形状的本质”,而不是具体的几何形状。 拓扑形变…...
mysql数据库(四)单表查询
单表查询 文章目录 单表查询一、单表查询1.1 简单查询1.2where1.3group by1.4having1.5order by1.6limit 一、单表查询 记录的查询语法如下: SELECT DISTINCT(去重) 字段1,字段2… FROM 表名 WHERE 筛选条件 GROUP BY 分组 HAVING 分组筛选 ORDER BY 排序 LIMIT 限…...

JavaEE初阶---properties类+反射+注解
文章目录 1.配置文件properities2.快速上手3.常见方法3.1读取配置文件3.2获取k-v值3.3修改k-v值3.4unicode的说明 4.反射的引入4.1传统写法4.2反射的写法(初识)4.3反射的介绍4.4获得class类的方法4.5所有类型的class对象4.6类加载过程4.7类初始化的过程4…...

HarmonyOS一次开发多端部署三巨头之功能级一多开发和工程级一多开发
功能级一多开发与工程级一多开发 引言功能级一多开发SysCaps机制介绍能力集canlUse接口 工程级一多开发三层架构规范 引言 一次开发多端部署 定义:一套代码工程,一次开发上架,多端按需部署 目标:支撑开发者快速高效的开发多终端设…...
STL常用遍历算法
概述: 算法主要是由头文件<algorithm> <functional> <numeric>组成。 <algorithm>是所有STL头文件中最大的一个,范围涉及到比较、 交换、查找、遍历操作、复制、修改等等 <numeric>体积很小,只包括几个在序列上面进行简…...
前端开发中常见的ES6技术细节分享一
var、let、const之间有什么区别? var: 在ES5中,顶层对象的属性和全局变量是等价的,用var声明的变量既是全局变量,也是顶层变量 注意:顶层对象,在浏览器环境指的是window对象,在 Node 指的是g…...

行业类别-智慧城市-子类别智能交通-细分类别自动驾驶技术-应用场景城市公共交通优化
1.大纲分析 针对题目“8.0 行业类别-智慧城市-子类别智能交通-细分类别自动驾驶技术-应用场景城市公共交通优化”的大纲分析,可以从以下几个方面进行展开: 一、引言 简述智慧城市的概念及其重要性。强调智能交通在智慧城市中的核心地位。引出自动驾驶…...

[High Speed Serial ] Xilinx
Xilinx 高速串行数据接口 收发器产品涵盖了当今高速协议的方方面面。GTH 和 GTY 收发器提供要求苛刻的光互连所需的低抖动,并具有世界一流的自适应均衡功能,具有困难的背板操作所需的 PCS 功能。 Versal™ GTY (32.75Gb/s)&…...

Unity学习笔记(3):场景绘制和叠层设置 Tilemap
文章目录 前言开发环境规则瓦片绘制拐角 动态瓦片总结 前言 这里学一下后面的场景绘制和叠层技巧。 开发环境 Unity 6windows 11vs studio 2022Unity2022.2 最新教程《勇士传说》入门到进阶|4K:https://www.bilibili.com/video/BV1mL411o77x/?spm_id_from333.10…...
不吹不黑,客观理性深入讨论中国信创现状
1. 题记: 随着美国大选尘埃落定,特朗普当选美国新一任总统,参考他之前对中国政策的风格,个人预计他将进一步限制中国半导体产业和信创产业的发展。本篇博文不吹不黑,客观理性深入探讨中国信创现状。文中数据来自权威媒…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...

UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...

springboot 日志类切面,接口成功记录日志,失败不记录
springboot 日志类切面,接口成功记录日志,失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...
区块链技术概述
区块链技术是一种去中心化、分布式账本技术,通过密码学、共识机制和智能合约等核心组件,实现数据不可篡改、透明可追溯的系统。 一、核心技术 1. 去中心化 特点:数据存储在网络中的多个节点(计算机),而非…...