当前位置: 首页 > 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;让复杂的集合操作变得高效…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

汽车生产虚拟实训中的技能提升与生产优化​

在制造业蓬勃发展的大背景下&#xff0c;虚拟教学实训宛如一颗璀璨的新星&#xff0c;正发挥着不可或缺且日益凸显的关键作用&#xff0c;源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例&#xff0c;汽车生产线上各类…...

屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!

5月28日&#xff0c;中天合创屋面分布式光伏发电项目顺利并网发电&#xff0c;该项目位于内蒙古自治区鄂尔多斯市乌审旗&#xff0c;项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站&#xff0c;总装机容量为9.96MWp。 项目投运后&#xff0c;每年可节约标煤3670…...

高防服务器能够抵御哪些网络攻击呢?

高防服务器作为一种有着高度防御能力的服务器&#xff0c;可以帮助网站应对分布式拒绝服务攻击&#xff0c;有效识别和清理一些恶意的网络流量&#xff0c;为用户提供安全且稳定的网络环境&#xff0c;那么&#xff0c;高防服务器一般都可以抵御哪些网络攻击呢&#xff1f;下面…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版

7种色调职场工作汇报PPT&#xff0c;橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版&#xff1a;职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

Linux 中如何提取压缩文件 ?

Linux 是一种流行的开源操作系统&#xff0c;它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间&#xff0c;使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的&#xff0c;要在 …...