当前位置: 首页 > news >正文

浅谈操作系统中的重要概念——线程(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)——设计模式

文章目录 一、什么是设计模式&#xff1f;二、单例模式2.1、饿汉模式2.2、懒汉模式2.3、多线程情况下调用 饿汉模式与懒汉模式 谁是安全的&#xff1f;&#xff1f;&#xff08;重点&#xff09; 三、工厂模式3.1、什么是工厂模式&#xff1f;3.1.1、构造方法存在的缺陷3.1.1.1…...

nginx配置域名与IP访问服务冲突问题

在最近的一次开发中遇到一个问题&#xff0c;我在云服务器上部署了两个服务&#xff0c;A服务和B服务&#xff0c; A服务在服务器中用的端口是80端口&#xff0c;所以我在浏览器访问的地址就是 B服务在服务器中用的是9818端口&#xff0c;所以我在浏览器访问的是 现在我给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 中用于条件渲染的指令&#xff0c;它们通常与 v-else 一起使用。下面我来详细解释一下它们的用法和区别&#xff1a; 1、v-if 用法&#xff1a;v-if 是一个指令&…...

linux进阶高级配置,你需要知道的有哪些2-firewalld防火墙(一)

1、防火墙的技术上分类&#xff1a; 包过滤&#xff1a;firewalld属于这种 应用代理&#xff1a; 状态检测&#xff1a;ASA 2、firewalld的两种配置模式&#xff1a; 运行时配置 &#xff1a;立即生效 永久配置&#xff1a;重新加载服务生效 3、常用的区域&#xff1a; trust…...

Centos 中如何汉化man命令

刚学Linux&#xff0c;记不住命令和选项&#xff0c;很依赖里面的 man 查看命令&#xff0c;但因为着实看不懂&#xff0c;有没有什么办法把man查看命令的信息改成中文 在CentOS 7中&#xff0c;你可以通过安装man-pages-zh包来获取中文的man手册。以下是具体的步骤&#xff1a…...

原生小程序开发如何使用 tailwindcss

原生小程序开发如何使用 tailwindcss 原生小程序开发如何使用 tailwindcss 前言什么是 weapp-tailwindcss ?0. 准备环境以及小程序项目1. 安装与配置 tailwindcss 0. 使用包管理器安装 tailwindcss1. 在项目目录下创建 postcss.config.js 并注册 tailwindcss2. 配置 tailwind…...

spring alibaba中的seata分布式事务

Seata AT 模式设计思路 一阶段&#xff1a;业务数据和回滚日志记录在同一个本地事务中提交&#xff0c;释放本地锁和连接资源。 核心在于对业务sql进行解决解析&#xff0c;转换成undolog&#xff0c;并同时入库存 二阶段&#xff1a; 提交异步化&#xff0c;非常快速地完成…...

MQTT学习(二)

订阅主题和订阅确认 SUBSCRIBE——订阅主题 之前的CONNECT报文&#xff0c;分为 固定报头&#xff1a;必须存在&#xff0c;用于描述报文信息。里面有指出什么类型的报文&#xff0c;报文的等级。可变报头&#xff1a;不一定存在。主要看什么样子类型的报文。有效载荷部分&a…...

入职Java,不会git被开除了。。。

入职Java&#xff0c;不会git被开除了。。。 文章目录 入职Java&#xff0c;不会git被开除了。。。前言一、Git是什么&#xff1f;二、Git的核心概念三、Git的工作流程四、Git的常用命令五、总结 &#x1f308;你好呀&#xff01;我是 山顶风景独好 &#x1f49d;欢迎来到我的博…...

Mysql 隔离级别

MySQL的事务隔离级别是指在处理并发事务时&#xff0c;为保证数据的一致性和事务的独立性&#xff0c;数据库系统提供的不同级别控制策略。根据ACID特性中的隔离性&#xff08;Isolation&#xff09;&#xff0c;MySQL支持四种标准的事务隔离级别&#xff0c;每种级别有不同的并…...

每日一学—K邻算法:在风险传导中的创新应用与实践价值

文章目录 &#x1f4cb; 前言&#x1f3af; K邻算法的实践意义&#x1f3af; 创新应用与案例分析&#x1f525; 参与方式 &#x1f4cb; 前言 在当今工业领域&#xff0c;图思维方式与图数据技术的应用日益广泛&#xff0c;成为图数据探索、挖掘与应用的坚实基础。本文旨在分享…...

基于Springboot的校园疫情防控信息管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的校园疫情防控信息管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层…...

【C++】内联函数、auto、范围for

文章目录 1.内联函数2.auto关键字2.1auto简介2.2auto的注意事项2.3auto不能推导的场景 3.基于范围的for循环(C11)4.指针空值nullptr(C11) 1.内联函数 概念&#xff1a; 以inline修饰的函数叫做内联函数&#xff0c;编译时C编译器会在调用内联函数的地方展开&#xff0c;没有函…...

Day 46 139.单词拆分

单词拆分 给定一个非空字符串 s 和一个包含非空单词的列表 wordDict&#xff0c;判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 说明&#xff1a; 拆分时可以重复使用字典中的单词。 你可以假设字典中没有重复的单词。 示例 1&#xff1a; 输入: s “leet…...

streamlit报错:AxiosError: Request failed with status code 403

解决办法&#xff1a; 步骤一&#xff1a;创建config.toml vi ~/.streamlit/config.toml 步骤二&#xff1a;加入以下内容 [server] enableXsrfProtection false enableCORS false步骤三&#xff1a;重新启动你的streamlit网页...

java基础教学 |Java Stream API详解

Java Stream API 是Java 8引入的一个重要特性&#xff0c;它为集合对象提供了一种新的计算模型&#xff0c;使得开发者能够以声明性的方式处理数据集合。Stream API 不仅提高了代码的可读性和简洁性&#xff0c;还极大地优化了并行处理能力&#xff0c;让复杂的集合操作变得高效…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误

HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误&#xff0c;它们的含义、原因和解决方法都有显著区别。以下是详细对比&#xff1a; 1. HTTP 406 (Not Acceptable) 含义&#xff1a; 客户端请求的内容类型与服务器支持的内容类型不匹…...

应用升级/灾备测试时使用guarantee 闪回点迅速回退

1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间&#xff0c; 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点&#xff0c;不需要开启数据库闪回。…...

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动&#xff08;如演唱会、马拉松赛事、高考中考等&#xff09;期间&#xff0c;城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例&#xff0c;暖城商圈曾因观众集中离场导致周边…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例

文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

视频字幕质量评估的大规模细粒度基准

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用&#xff0c;因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型&#xff08;VLMs&#xff09;在字幕生成方面…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)

在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...