单例模式和工厂模式

目录

今日良言:关关难过关关过,步步难行步步行

一、单例模式

1.饿汉模式

2.懒汉模式

二、工厂模式


今日良言:关关难过关关过,步步难行步步行

 

一、单例模式

首先来解释一下,什么是单例模式。

单例模式也就是单个实例(对象)。在有些场景中,只能创建出一个实例,不应该创建多个实例。

单例模式,就是针对上述的需求场景进行了个更强制的保证,通过巧用 java 现有的语法,达成了某个类 只能被创建出一个实例,这样的效果.(当程序猿不小心创建了多个实例,就会编译报错)。

单例模式最常见的两种就是:饿汉模式和懒汉模式。

1.饿汉模式

代码如下:

class Singleton{// 在此处就把实例给创建出来了private static Singleton instance = new Singleton();// 如果需要使用这个唯一的实例,统一通过这个方法获取public static Singleton getInstance() {return instance;}// 为了避免Singleton 类不小心被赋值出多份,将构造方法设置成private.// 此时就无法通过new 的方式来创建这个Singleton 实例了。private Singleton(){}
}
public class Exercise {public static void main(String[] args)  {Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s1 == s2);}
}

 

在类加载阶段就将实例创建好了,这种效果就给人一种“特别急切”的感觉。

被static修饰表示这个属性和实例无关,而是和类相关。

Java 代码中的每个类,都会在编译完成后得到.class 文件,JVM 运行时就会加载这个.class文件,读取其中的二进制指令,并且在内存中构造出对应过的类对象(形如:Singleton.class),具体的类加载可以阅读博主之前写的博客:

深度剖析JVM三个面试常考知识点_程序猿小马的博客-CSDN博客

由于 类对象在一个 Java 进程中是唯一的,因此这个类对象的内部的类属性也是唯一的。

static 在这里的作用有两个:

1)static 保证这个实例唯一。

2)static 保证这个实例确实在一定的时机被创建出来。

static 属于这个实现方式中的灵魂角色。

2.懒汉模式

代码实现:

class SingletonLazy {private static SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null) {instance = new SingletonLazy();}return instance;}// 构造方法设置成私有的private SingletonLazy(){}
}
public class Exercise {public static void main(String[] args)  {SingletonLazy s1 = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s1 == s2);}
}

这个实例并非是类加载阶段创建,而是真正第一次使用的时候才去创建, 如果不用就不创建了。

上述写的饿汉模式和懒汉模式是在单线程情况下的代码,如果在多线程下调用getInstance 是否是线程安全的呢?

答案是:一个是线程安全的,一个是线程不安全的。

饿汉模式是线程安全的,因为饿汉模式的 getInstance 方法只涉及到“读操作”。

懒汉模式是线程不安全的,因为懒汉模式的 getInstance 方法既有读操作又有写操作。

线程安全问题的详细解释,博主在之前的博客中有提到:

线程安全问题_程序猿小马的博客-CSDN博客

这里如果在多线程情况下调用懒汉模式的 getInstance 方法,会发生多次 new 操作,显然就不是单例了。

那么,如何让上述懒汉模式能够成为线程安全的呢?

加锁!!!

上述线程安全问题本质上是 修改操作不是原子的,因此,需要保证这个修改操作是原子的。

修改代码如下:

 此时,把锁加到外面,保证了读操作和修改操作是一个整体。

但是,代码写到这里,还有问题,上述这种写法,就导致了每次 getInstance 都需要加锁,加锁操作都是有开销的,仔细考虑一下,这里真的需要每次都加锁吗?

显然不是,这里的加锁只是在new出对象之前加上是有必要的,一旦对象 new 完以后,后续调用 getInstance ,此时 instance 一定是非空的,因此会直接 return。

基于上述讨论,就可以给刚才的代码加上一个判定:

如果对象还没创建才加锁,如果对象已经创建过了,就不加锁了。

修改代码如下:

此时,这里就不再是无脑加锁了,而是满足了特定条件之后,才真正加锁。 

注意:

这两个if 的作用不一样,第一个if 判断是否要加锁,第二个if 判断是否要创建对象。

加锁操作可能会引起线程阻塞,当执行到锁结束之后,执行到第二个 if 的时候,第二个 if 和第一个 if 之间可能已经隔了很久的时间了,instance 变量可能已经被别的线程给修改过了,所以需要第二次 if 判断当前线程是否需要创建对象。

上述代码其实还存在问题: 内存可见性问题以及指令重排序

关于这个问题,博主之前的博客也有详细介绍:

线程安全问题_程序猿小马的博客-CSDN博客 

内存可见性问题:

假设有很多线程都去进行 getInstance ,这个时候,可能会存在被优化的风险(只有第一次读的时候,才真正的读了内存,后续都是读寄存器)

指令重排序:

instance = new SingletonLazy();  

这个操作可以拆分为三个步骤:

1)申请内存空间。

2)调用构造方法,把这个内存空间初始化成一个合理的对象。

3)把内存空间的地址赋值给 instance 引用。

正常情况下,是按照 1 2 3 这个顺序执行代码,但是编译器存在指令重排序问题,编译器为了提高程序效率,会调整代码执行顺序, 1 2 3 可能就变成了 1 3  2 ,如果是单线程下,1 2 3 和 1 3 2 没有本质区别,但是多线程下就会出现问题了。 

假设线程 t1 是 按照 1 3 2 的步骤执行的,t1 执行到 1 3 之后,执行 2 之前,被切除 cpu ,此时 t2执行,(当 t1 执行完 3 之后,t2 看起来此处的引用就非空了)此时此刻,t2 就相当于直接返回了 instance 引用,并且可能会尝试使用引用中的属性,但是由于 t1 中的 2 操作还没执行完呢,t2 拿到的是非法的对象,还没构造完成的不完整的对象。

因此,需要解决上述问题,使用 volatile  !!!

修改代码如下:

 懒汉模式完整代码如下:

class SingletonLazy {private volatile static 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 Exercise {public static void main(String[] args)  {SingletonLazy s1 = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s1 == s2);}
}

二、工厂模式

先来解释一下什么是工厂模式。

工程模式用一句话概括:使用普通的方法,来代替构造方法创建对象。

为什么要代替呢? 这是因为构造方法有坑,坑就体现在,如果只构造一种对象,好办,如果要构造多种不同的情况,就不好办了。

举个例子:

假设现在有一个类,表示平面上的一个点

 上述构造方法表示使用笛卡尔坐标系提供的坐标来构造点。

如果这里假设再使用极坐标来构造点,代码如下:

很明显,这个代码存在问题,正常来说,多个构造方法,是通过“重载”的方式来提供的,重载要求的是 方法名相同,参数的个数或者类型不同。

 

为了解决这个问题,就可以使用工厂模式:

 普通方法的方法名没有限制,因此有多种方式构造,使用不同的方法名即可。

以上就是单例模式和工厂模式的介绍。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/23915.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【动态规划刷题 4】礼物的最大价值下降路径最小和

礼物的最大价值 在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值…

以指标驱动,保险、零售、制造企业开启精益敏捷运营的新范式

近日,以“释放数智生产力”为主题的 Kyligence 用户大会在上海前滩香格里拉大酒店成功举行。大会包含上午的主论坛和下午的 4 场平行论坛,并举办了闭门会议、Open Day 等活动。来自金融、零售、制造、医药等行业的客户及合作伙伴带来了超过 23 场主题演讲…

AI驱动的靶点发现综述

疾病建模和靶点识别是药物发现中最关键的初始步骤。传统的靶点识别是一个耗时的过程,需要数年至数十年的时间,并且通常从学术报告开始。鉴于其分析大型数据集和复杂生物网络的优势,人工智能在现代药物靶点识别中发挥着越来越重要的作用。该综…

奥威BI系统:零编程建模、开发报表,提升决策速度

奥威BI是一款非常实用的、易用、高效的商业智能工具,可以帮助企业快速获取数据、分析数据、展示数据。值得特别注意的一点是奥威BI系统支持零编程建模、开发报表,是一款人人都能用的大数据分析系统,有助于全面提升企业的数据分析挖掘效率&…

[Docker实现测试部署CI/CD----自由风格的CI操作[最终架构](5)]

目录 11、自由风格的CI操作(最终)Jenkins容器化实现方案修改 docker.sock 权限修改 Jenkins 启动命令后重启 Jenkins构建镜像推送到Harbor修改 daemon.json 文件Jenkins 删除构建后操作Jenkins 添加 shell 命令重新构建 Jenkins通知目标服务器拉取镜像目…

C#核心知识回顾——19.插入排序

1.插入排序的基本原理 871542639 两个区域 排序区 未排序区 用一个索引值做分水岭 未排序区元素 与排序区元素比较 插入到合适位置 直到未排序区清空 int[] arr { 8, 6, 7, 2, 9, 4 };//第一步//能取出未排序区…

IL汇编实现两数相加输出结果

话说前面没有实现IL汇编2数相加;鼓捣了一下,实现的代码如下; .assembly extern mscorlib {}.assembly Test{.ver 1:0:1:0}.module test.exe.method static void main() cil managed{.maxstack 8.entrypoint.locals init (int32 V_0, int3…

[FlareOn6]FlareBear

前言 apk的逆向,没有壳,但可能做的不是太多,没能想到整个算法的运作原理 分析 搜索flag会发现存在这么一个函数,那么显示flag的时候应该是熊会跳舞显示flag,只要满足熊happy和ecsstatic就可以,happy只要一直存在点击…

Open3D (C++) 计算矩阵的广义逆

目录 一、算法原理1、广义逆2、计算过程二、代码实现三、结果展示四、参考链接本文由CSDN点云侠原创,原文链接。爬虫网站自重,把自己当个人,爬些不完整的误导别人有意思吗???? 一、算法原理 1、广义逆 非方阵不存在逆,但是存在广义逆(伪逆)。对于一个矩阵

FPGA初步学习之串口发送模块【单字节和字符串的发送】

串口相关简介 UART 在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位,如图所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。 通常用…

springboot-mybatis的增删改查

目录 一、准备工作 二、常用配置 三、尝试 四、增删改查 1、增加 2、删除 3、修改 4、查询 五、XML的映射方法 一、准备工作 实施前的准备工作: 准备数据库表 创建一个新的springboot工程,选择引入对应的起步依赖(mybatis、mysql驱动…

【BASH】回顾与知识点梳理(五)

【BASH】回顾与知识点梳理 五 五. 数据流重导向5.1 什么是数据流重导向standard output 与 standard error output/dev/null 垃圾桶黑洞装置与特殊写法standard input &#xff1a; < 与 << 5.2 命令执行的判断依据&#xff1a; ; , &&, ||cmd ; cmd (不考虑指…

骑砍二 ATC MOD 使用教程与应用案例解析

骑砍二 ATC MOD 使用教程与应用案例解析 作者&#xff1a;blibli-财不外漏 / NEXUSMODS-PuepleKarmen 案例MOD依赖&#xff1a;ATC - Adonnay’s Troop Changer & AEW - Adonnay’s Exotic Weaponry & New Armor 文本编辑工具&#xff1a;VS Code&#xff08;推荐使用&…

图解架构 | SaaS、PaaS、IaaS/aPaaS平台是什么?aPaaS与PaaS有什么区别?

参考 图解架构 | SaaS、PaaS、IaaS:https://www.51cto.com/article/717315.html aPaaS平台是什么&#xff1f;aPaaS与PaaS有什么区别&#xff1f;&#xff1a;https://developer.aliyun.com/article/718714 aPaaS和PaaS的区别是什么&#xff1f; aPaaS和PaaS都可以完成软件的…

Linux中安装Node

安装 先从 官方网站 下载安装包&#xff0c;有时 node 版本太新会导致失败&#xff0c;详见下方的常见问题第2点 cd /home // 创建目录&#xff0c;将下载好的 node 安装包上传到此目录 mkdir Download mkdir /usr/local/lib/node解压 // 解压&#xff0c;前面是文件当前路径…

iMX6ULL应用移植 | 移植 infoNES 模拟器(重玩经典NES游戏)

没玩过NES游戏的童年&#xff0c;可能不是80后的童年。我们小时候是从玩FC开始接触游戏机的&#xff0c;那时真的是红极一时啊&#xff0c;我上初中时还省吃俭用买了一台小霸王&#xff0c;暑假里把电视机都给打爆了&#xff01;那时任天堂单是FC机的主机的发售收入就超过全美的…

循环结构的学习

循环结构 文章目录 为什么要学习循环while循环dowhile循环偶数之和断点调试购物结算循环的选择类名和全类名摄氏华氏对照表for循环for执行次序五门功课成绩for的特殊写法break和continue录入客户信息_continue使代码优雅小数的比较不能用或! 为什么要学习循环 在编写代码时&a…

Spring集成Web

目录 1、简介 2、监听器 3、Spring提供的listener 3.1、xml 3.2、配置类 3.3、WebApplicationContextUtils 3.4、说明 4、自己复现的listener 4.1、ContextLoaderListener 4.2、WebApplicationContextUtils 4.3、Web调用 ⭐作者介绍&#xff1a;大二本科网络工程专业…

vue2-v-show和v-if有什么区别,使用场景分别是什么?

1、v-show和v-if的共同点 在vue中&#xff0c;v-if和v-show的作用效果是相同的&#xff08;不含v-else&#xff09;&#xff0c;都能控制元素在页面是否显示&#xff0c;在用法上也相同。 当表达式为true的时候&#xff0c;都会占据页面的位置 当表达式为false的时候&#xff…

AI介绍——chat gpt/文心一言/claude/bard/星火大模型/bing AI

AI体验 1. AI 介绍&#xff08;注册和使用&#xff09;1.1 Chat GPT1.2 文心一言1.3 Slack 上的 Claude1.3.1 Claude 介绍1.3.2 Claude 使用 1.4 Google的Bard1.4.1 Bard 介绍1.4.2 Bard 使用 1.5 科大讯飞的星火大模型1.5.1 星火大模型 介绍1.5.2 星火大模型 使用 1.6 new bin…