浅谈操作系统中的重要概念——线程(3)——设计模式
文章目录
- 一、什么是设计模式?
- 二、单例模式
- 2.1、饿汉模式
- 2.2、懒汉模式
- 2.3、多线程情况下调用 饿汉模式与懒汉模式 谁是安全的??(重点)
- 三、工厂模式
- 3.1、什么是工厂模式?
- 3.1.1、构造方法存在的缺陷
- 3.1.1.1、构造方法的名字必须固定是类名
一、什么是设计模式?
设计模式就相当于菜谱,有了菜谱/秘籍,就能够根据菜谱上的指引/步骤做出许多从前不会的美食,就算不会下厨的人,拥有了食谱,他的厨艺也能够得到提升和保障。
因此设计模式就是程序员的菜谱,设计模式中介绍了许多典型场景,以及针对这些典型场景的处理办法。按照设计模式来写的代码不会很差,在一定的规范范围里。
设计模式有很多种,不止23种,今天主要介绍两种常见、常用的:
1、单例模式
2、工厂模式
二、单例模式
单例模式对应的场景:希望代码中的有些对象在整个程序中只有一个实例(即对象只能 new 一次)。
譬如说:JDBC中的数据源DataSource这样的对象就只需要一个即可,因此这个对象就可以使用单例模式。
对于我们程序员来说,如果有些对象在整个程序中只需要有一个实例即可,那我们程序员就只new一次就好了,为什么还需要引入单例模式??那是因为对于程序来说,人是不靠谱的,就需要引入单例模式,此时由编译器来进行严格的检查(对于代码中只能new一次的对象,如果尝试new了多次,编译器就会直接报错来进行提示),确保某些对象处于单例模式。
接下来介绍一下在Java中单例模式的两种写法:
2.1、饿汉模式
饿汉模式:程序启动进行类加载之后,立即创建出实例。(比较迫切)
代码例子:
class Singleton{/*** 此处就期望类Singleton只有一个实例*/// 静态成员:instance
// static 静态的,与类相关的,整个程序中只有一份private static Singleton instance = new Singleton();// 通过此方法,随时获取到刚刚的 instance 变量public static Singleton getInstance(){return instance;}// 作出限制:禁止别人去 new 这个类的实例——>将构造方法变成privateprivate Singleton(){}
}public class testHunger {public static void main(String[] args) {Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();
// 此时我们再new类的实例,直接编译报错(编译器校验了)
// Singleton s3 = new Singleton();// 我们可以发现 s1、s2 这两个都是同一个对象System.out.println(s1.equals(s2)); //true
// s1、s2 和 s3 不是同一个对象
// System.out.println(s3.equals(s1)); //false
// System.out.println(s3.equals(s2)); //false}
}
但是上述代码中,我们将类的构造方法改成private,此时别人就一定不能创建多个实例了吗??其实还是可以通过 反射机制 ,在当前单例模式中,创建出多个实例。
但是反射属于 “非常规” 的编程手段,正常开发的时候,慎重使用,因为 滥用反射,会给代码带来极大风险,譬如会让代码变得抽象,日后难以维护,还破坏了Java的封装性。
当然Java中也有其他方式书写单例模式来防止反射,但此处不做过多介绍,可以自行查阅资料了解。
2.2、懒汉模式
懒汉模式:第一次使用实例的时候才创建实例,否则能不创建就不创建。(比较佛系)
/*** 代码示例:* 单例模式中的懒汉模式*/
class SingletonLazy{private static SingletonLazy instance = null;public static SingletonLazy getInstance(){if(instance == null){instance = new SingletonLazy();}return instance;}private SingletonLazy(){}
}public class testLazy {public static void main(String[] args) {SingletonLazy s1 = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();// 同样也是可以通过非常规手段:反射机制 获取类的实例Class<SingletonLazy> s3 = SingletonLazy.class;System.out.println(s3.equals(s1));System.out.println(s1.equals(s2));}
}
2.3、多线程情况下调用 饿汉模式与懒汉模式 谁是安全的??(重点)
饿汉模式:

懒汉模式:


上述逻辑,也是一个经典的面试题!
那我们如何保证懒汉模式的线程安全呢?
由于上述引起线程安全的原因是 if语句与new语句不是一个整体,因此出现了线程安全问题,此时我们通过将if语句和new语句打包成一个整体来解决懒汉模式的线程安全问题。

加锁之后,线程在cpu上的执行流程:(当加了锁之后,线程t2执行if语句时,就会发现此时的instance是一个非null的值,)

虽然加了锁,解决了线程安全问题,但是还存在问题:加锁是一个高成本的操作,会引起阻塞等待。加锁的基本原则应该是:非必要,不加锁。不能无脑加锁,如果无脑加锁,就会导致程序的执行效率受到影响。
此时我们对懒汉模式的代码中加的锁,导致后续每次调用 getInstance() 方法都要加锁,但是这是不合理且不必要的。**懒汉模式线程不安全主要是因为首次 new 对象时,才存在的。一旦将对象 new 出来后,后续再调用 getInstance() 方法就不存在线程安全问题了。**但是我们现在的加锁是:首次 new 对象调用时,加锁了。后续调用,也加锁了。但实际上后续调用不必加锁,因为后续调用后if条件就进不去了,此时也就不再涉及到修改操作了,全是读操作。但我们把不该加锁的地方加上锁了,很影响程序的执行效率。
对加锁操作做出以下修改:

两个if语句之间存在加锁操作,加锁就会引起线程阻塞等待,究竟阻塞等待多久,不知道,有可能第一个if语句与第二个if语句间隔沧海桑田,因此在这个长久的时间间隔里,可能别的线程就把instance改了。
上述对加锁操作做了修改之后,还存在一个问题:

已知一般 instance = new SingletonLazy();可以大致分成3个步骤:
1、给对象创建出内存空间,得到内存地址。
2、在空间上调用构造方法,对对象进行初始化。
3、把内存地址赋值给 instance引用。

假设现在代码的执行顺序由123变成132,但是在执行步骤3之后,进行了线程切换,此时还没来得及执行步骤2,即给对象初始化,就调度给别的线程了,此时别的线程执行的时候,判断instance不为空了,于是就直接返回instance,并且后续代码中可能会使用 instance 中的一些属性或者方法,但是由于多线程下出现了指令重排序的问题导致线程安全问题,此时的instance是个空引用,即拿到的这个对象,是个没有进行初始化,不完整的对象。
解决办法:给变量 instance 加上 volatile 之后,此时针对 instance 的赋值操作,就不会产生上述的指令重排序了,其执行顺序必然是遵循1、2、3执行。
加上 volatile 还有一个另外的用途:避免此处赋值操作的指令重排序。
懒汉模式的最终代码版本:
/*** 代码示例:* 单例模式中的懒汉模式*/
class SingletonLazy{private static volatile SingletonLazy instance = null;public static SingletonLazy getInstance(){if (instance == null){synchronized (SingletonLazy.class){if(instance == null){instance = new SingletonLazy();}}}return instance;}private SingletonLazy(){}
}public class testLazy {public static void main(String[] args) {SingletonLazy s1 = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();// 同样也是可以通过非常规手段:反射机制 获取类的实例Class<SingletonLazy> s3 = SingletonLazy.class;System.out.println(s3.equals(s1));System.out.println(s1.equals(s2));}
}
懒汉模式代码的3大要点:
1、加锁
2、双重if
3、volatile
从字面上理解/区分 饿汉模式和懒汉模式 会比较抽象,举2个例子加深一下理解:
譬如说日常生活中,吃完饭之后需要洗碗,有的人习惯吃完饭后立即洗碗,有的人习惯吃完饭后将碗放到一边,等到下顿饭要吃的时候才洗碗。一般大家会觉得吃完饭之后立即洗碗是一种高效率的生活方式,但其实在不考虑卫生的情况下,吃完饭后不洗碗,等到下次开饭再洗碗然后接着用,其实效率会更高。比如说这顿吃完有4个碗,下顿开饭只需要用到2个碗,此时就只需要洗2个碗来用就行了,效率大大提高了。
还譬如说:假设编辑器打开一个很大的文件,有的编辑器会一下尝试把所有内容都加载到内存中,再显示出来,这是典型的饿汉模式。有的编辑器,则只加载一部分内容(一个屏幕能显示的内容),其他部分,当用户翻页想要浏览时,再加载出来,这是典型的懒汉模式。
三、工厂模式
3.1、什么是工厂模式?
工厂,顾名思义是用来生产的。那么对应到我们代码上,工厂模式就是用来生产对象的。那么具体是怎么进行生产对象的呢??一般我们创建对象都是通过 new 的形式,使用构造方法来把对象创建出来,但构造方法存在一些缺陷,因此此时就可以使用 工厂模式 解决 上述问题。
3.1.1、构造方法存在的缺陷
3.1.1.1、构造方法的名字必须固定是类名
但是有时候有的类需要多种不同的构造方式,可由于构造方法名必须与类名一致,此时就只能使用方法重载(参数的个数和类型需要有差别)的方式来区分了。
譬如有一个坐标系类 Point。在此类中想要通过两种构造方式进行创建对象,一种是笛卡尔坐标系构造,一种是按照极坐标构造:
public class Point{public Point(double x,double y){}public Point(double r,double a){}
}
这两种构造方式的意义并不一样,但是使用构造方法表示出来时,由于其参数个数和类型都一致,因此无法构成方法重载,因此上述代码就会直接编译报错。那此时就可以使用工厂模式来解决上述问题:1、即不使用构造方法了,使用普通的方法来构造对象,这样方法名字就可以是任意的了。2、在普通方法的内部,再来new对象。
由于普通方法的目的是为了创建出对象来,因此这样的普通方法一般得是静态的。(因为要创建实例却又依赖实例,因此这样的普通方法设定为 静态的)
public class Point{
//由于这两方法的方法名不同,此时不会编译报错,public static makePointXY(double x,double y){//我们再在方法内部 new 出对象Point p = Point.makePointXY(10,20);}public static makePointRA(double r,double a){}
}
这样的操作,我们就称为 “工厂模式”,这样的方法我们就称为 “工厂方法”
相关文章:
浅谈操作系统中的重要概念——线程(3)——设计模式
文章目录 一、什么是设计模式?二、单例模式2.1、饿汉模式2.2、懒汉模式2.3、多线程情况下调用 饿汉模式与懒汉模式 谁是安全的??(重点) 三、工厂模式3.1、什么是工厂模式?3.1.1、构造方法存在的缺陷3.1.1.1…...
nginx配置域名与IP访问服务冲突问题
在最近的一次开发中遇到一个问题,我在云服务器上部署了两个服务,A服务和B服务, A服务在服务器中用的端口是80端口,所以我在浏览器访问的地址就是 B服务在服务器中用的是9818端口,所以我在浏览器访问的是 现在我给B服务…...
2024OD机试卷-字符串序列判定 (java\python\c++)
题目:字符串序列判定 题目描述 输入两个字符串 S 和 L ,都只包含英文小写字母。S长度 ≤ 100,L长度 ≤ 500,000。判定S是否是L的有效子串。 判定规则:S 中的每个字符在 L 中都能找到(可以不连续),且 S 在L中字符的前后顺序与 S 中顺序要保持一致。(例如,S = ” ace…...
7-128 最长公共子串
一个序列中去掉若干(也可以不去掉)元素剩下的部分称为其子序列。对于给定的序列X = <x1,x2,…,xm>,称序列Z = <z1,z2,…,zk>为X的一个子序列,仅当在X中存在一个递增序号序列<i1,i2,…,ik>,对所有的j(1,2,…,k)满足 xij…...
【瑞萨RA6M3】2. UART 实验
https://blog.csdn.net/qq_35181236/article/details/132789258 使用 uart9 配置 打印 void hal_entry(void) {/* TODO: add your own code here */fsp_err_t err;uint8_t c;/* 配置串口 */err g_uart9.p_api->open(g_uart9.p_ctrl, g_uart9.p_cfg);while (1){g_uart9.…...
js遇到需要正则匹配来修改img标签+清除行内样式
方法一 var regex0 new RegExp("(i?)(\<img)([^\>]\>)", "gmi") //正则匹配表达式this.newcontent this.content.replace(regex0,"$2 styledisplay:block;margin: auto;width:120px; $3") //下面这个则需要在$2 $3左右添加和修改东…...
Vue学习v-if与v-else-if
Vue学习v-if与v-else-if 一、前言1、v-if2、v-else-if3、v-else4、示例 一、前言 v-if 和 v-else-if 是 Vue.js 中用于条件渲染的指令,它们通常与 v-else 一起使用。下面我来详细解释一下它们的用法和区别: 1、v-if 用法:v-if 是一个指令&…...
linux进阶高级配置,你需要知道的有哪些2-firewalld防火墙(一)
1、防火墙的技术上分类: 包过滤:firewalld属于这种 应用代理: 状态检测:ASA 2、firewalld的两种配置模式: 运行时配置 :立即生效 永久配置:重新加载服务生效 3、常用的区域: trust…...
Centos 中如何汉化man命令
刚学Linux,记不住命令和选项,很依赖里面的 man 查看命令,但因为着实看不懂,有没有什么办法把man查看命令的信息改成中文 在CentOS 7中,你可以通过安装man-pages-zh包来获取中文的man手册。以下是具体的步骤:…...
原生小程序开发如何使用 tailwindcss
原生小程序开发如何使用 tailwindcss 原生小程序开发如何使用 tailwindcss 前言什么是 weapp-tailwindcss ?0. 准备环境以及小程序项目1. 安装与配置 tailwindcss 0. 使用包管理器安装 tailwindcss1. 在项目目录下创建 postcss.config.js 并注册 tailwindcss2. 配置 tailwind…...
spring alibaba中的seata分布式事务
Seata AT 模式设计思路 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。 核心在于对业务sql进行解决解析,转换成undolog,并同时入库存 二阶段: 提交异步化,非常快速地完成…...
MQTT学习(二)
订阅主题和订阅确认 SUBSCRIBE——订阅主题 之前的CONNECT报文,分为 固定报头:必须存在,用于描述报文信息。里面有指出什么类型的报文,报文的等级。可变报头:不一定存在。主要看什么样子类型的报文。有效载荷部分&a…...
入职Java,不会git被开除了。。。
入职Java,不会git被开除了。。。 文章目录 入职Java,不会git被开除了。。。前言一、Git是什么?二、Git的核心概念三、Git的工作流程四、Git的常用命令五、总结 🌈你好呀!我是 山顶风景独好 💝欢迎来到我的博…...
Mysql 隔离级别
MySQL的事务隔离级别是指在处理并发事务时,为保证数据的一致性和事务的独立性,数据库系统提供的不同级别控制策略。根据ACID特性中的隔离性(Isolation),MySQL支持四种标准的事务隔离级别,每种级别有不同的并…...
每日一学—K邻算法:在风险传导中的创新应用与实践价值
文章目录 📋 前言🎯 K邻算法的实践意义🎯 创新应用与案例分析🔥 参与方式 📋 前言 在当今工业领域,图思维方式与图数据技术的应用日益广泛,成为图数据探索、挖掘与应用的坚实基础。本文旨在分享…...
基于Springboot的校园疫情防控信息管理系统(有报告)。Javaee项目,springboot项目。
演示视频: 基于Springboot的校园疫情防控信息管理系统(有报告)。Javaee项目,springboot项目。 项目介绍: 采用M(model)V(view)C(controller)三层…...
【C++】内联函数、auto、范围for
文章目录 1.内联函数2.auto关键字2.1auto简介2.2auto的注意事项2.3auto不能推导的场景 3.基于范围的for循环(C11)4.指针空值nullptr(C11) 1.内联函数 概念: 以inline修饰的函数叫做内联函数,编译时C编译器会在调用内联函数的地方展开,没有函…...
Day 46 139.单词拆分
单词拆分 给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 说明: 拆分时可以重复使用字典中的单词。 你可以假设字典中没有重复的单词。 示例 1: 输入: s “leet…...
streamlit报错:AxiosError: Request failed with status code 403
解决办法: 步骤一:创建config.toml vi ~/.streamlit/config.toml 步骤二:加入以下内容 [server] enableXsrfProtection false enableCORS false步骤三:重新启动你的streamlit网页...
java基础教学 |Java Stream API详解
Java Stream API 是Java 8引入的一个重要特性,它为集合对象提供了一种新的计算模型,使得开发者能够以声明性的方式处理数据集合。Stream API 不仅提高了代码的可读性和简洁性,还极大地优化了并行处理能力,让复杂的集合操作变得高效…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...
一些实用的chrome扩展0x01
简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序,无论是测试应用程序、搜寻漏洞还是收集情报,它们都能提升工作流程。 FoxyProxy 代理管理工具,此扩展简化了使用代理(如 Burp…...
