多线程代码设计模式之单例模式

目录

设计模式引入

饿汉模式

懒汉模式

单例模式总结


设计模式引入

1.1.什么是设计模式

(1)设计模式就是一种代码的套用模板。例如:一类题型的步骤分别有哪些,是可以直接套用的。

(2)像棋谱,也是一种设计模式。根据棋谱去下棋,棋术自然不会很差。

(3)官方定义就是说:设计模式就是一套经过反复使用、多人知晓、经过分类、代码设计经验的总结

(4)使用代码设计模式可以提高代码的下限。更容易被人理解,使用等等

本文我们主要介绍设计模式中的单例设计模式

1.2.单例设计模式

(1)单例模式,就是只有单个对象。

(2)单例模式,在整个代码进程中的某个类,有且只会产生一个对象,也不会多new出来前提的对象(这个类就是单例模式,它只会产生一个对象)

(3)如何保证只会有一个对象呢?那就需要程序员通过代码去设计,用代码去限制和规范。

(4)根本上可以保证对象是唯一的,这种设计模式就称为单例模式

(5)单例模式,本节内容介绍两种最常用的:饿汉模式和懒汉模式

饿汉模式

1.1.概念引入

(1)什么是饿汉模式?听名字就是一个很饿的汉子

(2)饿汉模式,就是在类加载的时候,就完成了对象的实例化

(3)我们把迫不及待的实例化对象这种行为,称为饿汉模式

1.2.代码设计

(1)类体

这个名字是可以随便起的 

class Singleton {}

(2)实例化对象

这里直接实例化一个饿汉对象,赋值给instance引用。这个引用是private static类型的,外部访问不到,而且在类加载的时候,就把对象给初始化好了。 

class Singleton {private static Singleton instance = new Singleton();//一加载就实例化对象,称为饿汉
}

(3)提供获取对象的方法

class Singleton {private static Singleton instance = new Singleton();//一加载就实例化对象,称为饿汉//用于外部获取饿汉对象public static Singleton getInstance() {return instance;}private Singleton() {//私有构造方法,外部无法再进实例化}
}

外部通过调用这个方法,就可以拿到对象的一个引用;多次调用改方法,获得的对象都是同一份。 

(4)私有构造方法

这是最关键的一步,将构造方法设置为私有的,外部就无法在new对象了。下面这也是“饿汉模式”的完整代码了

class Singleton {private static Singleton instance = new Singleton();//一加载就实例化对象,称为饿汉//用于外部获取饿汉对象public static Singleton getInstance() {return instance;}private Singleton() {//私有构造方法,外部无法再进实例化}
}

(5)验证是否同一个对象

 public static void main(String[] args) {Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s1 == s2);}

我们发现,这两个对象都是一样的。

(6)验证外部不可new

以上就饿汉模式代码设计的全部了,还有一些小问题,下面接着介绍。

1.3.线程安全等问题

(1)线程安全问题

在多线程代码中,对于饿汉设计模式的代码,是线程安全的。

原因:饿汉模式在加载类的时候,对象就已经实例化好了,比线程通过get方法去获得改对象的引用更快;后续线程获取对象的时候,都是获取到的同一个变量。

(2)保证唯一对象问题

初心:类似饿汉模式的代码设计,都是提供给外部使用的,外部是无法new对象的;即使内部可以,但是设计者不会那么蠢。

外部new对象怎么办:外部是可以通过反射的方式再new一次对象,但是这种方式我们不考虑,反射本身的开销和成本也很大。就像有人下定决心要偷你家东西,你也无法防住。

懒汉模式

1.1.概念引入

(1)听名字,看似懒,其实是高效率

(2)懒汉模式:类很懒,当程序员需要对象而去调用时,它才会实例化对象

(3)我们把这种不着急实例化对象的,称为“懒汉模式”

1.2.代码设计 

(1)类体

老样子,名字随意 

class SingletonLazy {}

(2)对象的引用

因为很懒,所以一开始是不实例化对象的 

class SingletonLazy {private static SingletonLazy instance = null;//一开始不初始化对象}

(3)对外开放对象

只有在第一次调用改方法时,才会去实例化一个对象 

class SingletonLazy {private static SingletonLazy instance = null;//一开始不初始化对象public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象if(instance == null) {instance = new SingletonLazy();}return instance;}}

(4)关闭构造方法

只有这样,外部才没有办法去new对象 

class SingletonLazy {private static SingletonLazy instance = null;//一开始不初始化对象public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象if(instance == null) {instance = new SingletonLazy();}return instance;}private SingletonLazy() {}
}

上面就是一个和饿汉模式类似的代码结构了

(5)同一个对象

 public static void main(String[] args) {SingletonLazy s1  = SingletonLazy.getInstance();SingletonLazy s2  = SingletonLazy.getInstance();System.out.println(s1 == s2);}

(6)外部不可new

这里打断一下,上面的代码并不完整,因为上面的代码是一个线程不安全的代码。

线程安全的完整代码:接下来介绍

class SingletonLazy {private static volatile SingletonLazy instance = null;//一开始不初始化对象private static Object locker = new Object();public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象if(instance == null) {synchronized (locker) {if(instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() {}
}
1.3.线程安全问题

(1)为什么不安全?

在类加载完之后,并没有实例化出对象;此时两个线程去调用getInstance()方法,就很大可能会创造出两个对象,这就违背了一个对象原则了。

(2)为什么会有线程不安全问题?

我们从指令的执行和线程调度方面切入分析。下面都是按照发生线程安全问题的情况下

1)有两个线程t1、t2,先后调用了改方法,并且下面的条件都成立,并且进入

if(instance == null)

像这样先后进入if语句之后,他们就会创造出两个实例,这就是线程不安全的问题。

上面产生的问题就是由于操作不是原子性造成的,我们只需要进行加锁操作就好。 

1.4.设计线程安全代码

(1)加锁之后

private static Object locker = new Object();
public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象synchronized (locker) {if(instance == null) {instance = new SingletonLazy();  }}return instance;
}

加锁之后,同一个时间就只会有一个线程进入if语句并且实例化对象,当该线程解锁之后,对象已经创造好了;此时第二个线程不再阻塞,并进入if语句,但是此时条件已经不成立便不会进入。此时,就不会由于多线程代码而产生问题了。

(2)判断是否要加锁

上面的代码产生线程安全问题的主要原因是:由于对象没有实例化好,但是,当对象实例化好之后再去调用该方法,是不会产生任何线程问题的。此时,锁就现得很笨重,因此,我们只需要让第一次调用时加锁就好,于是得出下面的改进代码

private static Object locker = new Object();
public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象if(instance == null) {synchronized (locker) {if(instance == null) {instance = new SingletonLazy();}}}return instance;
}

这就是双重if判断,但是两个if语句发挥的作用不一样。

第一个if:判断是否要加锁

第二个if:判断是否要创建对象

(3)预防指令重排序

上述代码就是最终的安全代码了吗?不,还不是,还会存在一个问题,那就是指令重排序造成的线程安全问题。

instance = new SingletonLazy();

new对象这个代码大概可以分成三步

1.申请内存空间

2.调用构造方法(对内存空间进行初始化)

3.把此时内存空间的地址,赋值给instance引用

指令执行的顺序大概有两种:123或者132。在单线程中,两种顺序都不会出现问题,但是在多线程中,132的顺序是会出现问题的。

问题:当执行的顺序是132时,t1线程执行完3之后,也就是赋值给了instance引用,此时不为空,然后被调度走,t2线程就可以返回这个引用,此时这个对象内部是没有初始化的,里面的值都是0,t2线程拿着这个引用去做一些事情,就会引起一些问题。

所以,我们要加上volatile关键字。

private static volatile SingletonLazy instance = null;//一开始不初始化对象

可见,volatile关键字表面上是预防内存可见性问题,更深的是预防指令重排序问题。给变量加上,可以预防对该变量发生一下指令重排序,可以保证intance引用存放的是完整对象。

线程安全的懒汉模式代码:

class SingletonLazy {private static volatile SingletonLazy instance = null;//一开始不初始化对象private static Object locker = new Object();public static SingletonLazy getInstance() {//什么时候调用,才会初始化对象if(instance == null) {synchronized (locker) {if(instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() {}
}

单例模式总结

1.单例模式引用场景

(1)在实际的业务中,有的类,只需要有一个对象就够了

(2)比如,要写一个类,用来实现一些功能,可以加载上百G的数据。所以这个类只需要创造一个对象,就可以管理完这些数据了。如果是多个实例,消耗的内存也更加大。

2.懒汉模式的优势

(1)在程序加载中,如果有多个单例模式且是饿汉模式,那么在加载的时候就需要花费很多的时间。

(2)如果是懒汉模式,在程序加载的时候不会一下子创造出很多的对象,因此,时间效率就得到了提高。


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

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

相关文章

代码随想录算法训练营DAY17|C++二叉树Part.4|110.平衡二叉树、257.二叉树的所有路径、404.左叶子之和

文章目录 110.平衡二叉树思路伪代码CPP代码 257.二叉树的所有路径思路伪代码实现CPP代码 404.左叶子之和思路伪代码CPP代码 110.平衡二叉树 力扣题目链接 文章讲解:110.平衡二叉树 视频讲解:后序遍历求高度,高度判断是否平衡 | LeetCode&…

lua学习笔记6(经典问题输出99乘法表)

print("************for循环的99乘法表*************") for i 1, 9 dolocal line "" -- 创建一个局部变量来累积每行的输出--local 是一个关键字,用于声明一个局部变量。for j 1, i doline line .. j .. "*" .. i .. ""…

电脑桌面上表格不见了怎么找回?这5个方法不要错过

在日常的办公和学习中,电脑桌面上的各种文件、文件夹和表格等无疑是我们较为频繁使用的资源。然而,有时我们可能会因为一些操作失误或者电脑问题,突然发现桌面上的某个表格文件神秘失踪了。面对这种情况,很多人可能会感到焦虑和不…

[WIP]Sora相关工作汇总VQGAN、MAGVIT、VideoPoet

视觉任务相对语言任务种类较多(detection, grounding, etc.)、粒度不同 (object-level, patch-level, pixel-level, etc.),且部分任务差异较大,利用Tokenizer核心则为如何把其他模态映射到language space,并能让语言模型更好理解不同的视觉任…

Python-VBA函数基础知识-001

一、函数的定义: 函数(Function)是一段可重复使用的代码块,用于执行特定的任务或计算,并可以接受输入参数和返回输出结果。函数可以将复杂的问题分解为更小的子问题,提高代码的可读性和可维护性。 二、函数的组成: 在…

Spring Boot集成JWT快速入门demo

1.JWT是什么? JWT,英文全称JSON Web Token:JSON网络令牌。为了在网络应用环境间传递声明而制定的一种基于JSON的开放标准(RFC 7519)。这个规范允许我们使用JWT在客户端和服务端之间传递安全可靠的信息。JWT是一个轻便的安全跨平台传输格式&am…

前端零基础学习web3开发

目录 1 钱包 2 发起交易 3 出块 4 块高 5 矿工 6 Gas费 这一节,我们不说让人神往的比特币,不说自己会不会利用这个虚拟的货币来发财,也不说那些模模糊糊的知识,什么去中心化啦,什么奇妙的加密啦,我们…

AI 驱动强大是视频转换处理软件

由 AI 驱动的视频工具包。 增强、转换、录制和编辑视频AI 驱动的顶级视频工具包。 不论是老旧、低质、噪声或模糊的影片/图像,都能升级至 4K,稳定抖动的影片,提升帧率至 120/240fps,并能以全面 GPU 加速进行转换、压缩、录制和编辑…

盘点那些好用的SAP FIORI App (四)-应收账期报告

这个App的ID是IDCNAR, 其实也是一个T-Code, 也就是说,不光在FIORI app里面可以使用,在SAP GUI里面也是存在的,这个就属于我另一篇里面提到的,GUI和FIORI都可以使用的功能,但是前提是S4 HANA平台 操作的界面非常简单&am…

linux进阶篇:磁盘管理(一):LVM逻辑卷基本概念及LVM的工作原理

Linux磁盘管理(一):LVM逻辑卷基本概念及LVM的工作原理 一、传统的磁盘管理 在传统的磁盘管理方案中,如果我们的磁盘容量不够了,那这个时候应该要加一块硬盘,但是新增加的硬盘是作为独立的文件系统存在的,原有的文件系…

即插即用篇 | RTDETR引入Haar小波下采样 | 一种简单而有效的语义分割下采样模块

本改进已集成到 RT-DETR-Magic 框架。 下采样操作如最大池化或步幅卷积在卷积神经网络(CNNs)中被广泛应用,用于聚合局部特征、扩大感受野并减少计算负担。然而,对于语义分割任务,对局部邻域的特征进行池化可能导致重要的空间信息丢失,这有助于逐像素预测。为了解决这个问…

接口日志处理类

类:ZCL_IFLOG_UTILITIES 属性:AUTH_RESULTS_LIST 类型: TY_AUTH_RESULT Private 受保护部分: PRIVATE SECTION.TYPES: BEGIN OF ty_auth_result,funcname TYPE ztall_logcfg-funcname,pass TYPE abap_bool,END OF ty_aut…

商城系统如何设计表

小商城:参考千小夜小程序 大商城: 首先根据某个商品的三级分类进来后,我们找到在这个分类下该商品的所有属性(也就是泛指该商品不管怎么样都有这些属性),这里指的属性是规格包装,也就是基本属性…

线程池CompletableFuture异步编排复习笔记

一、线程回顾 1.1 初始化线程的 4 种方式 1)、继承 Thread public static class Thread01 extends Thread {Overridepublic void run() {System.out.println("当前线程:" Thread.currentThread().getId());int i 10 / 2;System.out.print…

机器学习周记(第三十三周:文献阅读[GWO-GART])2024.4.1~2024.4.7

目录 摘要 ABSTRACT 1 论文信息 1.1 论文标题 1.2 论文摘要 1.3 论文数据集 1.4 论文模型 2 相关知识 摘要 本周阅读了一篇使用GAT结合GRU预测PM2.5浓度的文章。论文模型为图注意力循环网络(GART),首次提出了一种新型的多层GAT架构&…

AI预测福彩3D第27弹【2024年4月5日预测--第4套算法重新开始计算第12次测试】

今天继续按照合并后的算法进行测试,因为本套算法的命中率较高。以后有时间的话会在第二篇文章中发布排列3的预测结果。好了,废话不多说了,先上预测结果图,再上综合预测结果~ 2024年4月5日福彩3D的七码预测结果如下 第一套…

关于代码审查的一些思考

作为一名代码审查员,首先我们已经具备了丰富的代码开发经验,并且对提交的代码工程非常熟悉 代码审查可以发现并纠正代码中的错误、缺陷和不良实践。通过多人对代码进行仔细的检查和讨论,能够发现一些单独开发时难以察觉的问题,从…

5G智慧水利数字孪生可视化平台,推进水利行业数字化转型

5G智慧水利数字孪生可视化平台,推进水利行业数字化转型。随着5G技术的快速发展,越来越多的行业开始探索数字化转型的道路。水利行业作为国民经济的重要支柱,也面临着数字化转型的迫切需求。5G智慧水利数字孪生可视化平台作为水利行业数字化转…

Integer的缓存机制

LeetCode练习题--567.字符串的排列 今天刷题的时候,突然发现了一个问题: 为什么明明是相同的Integer值,有的时候使用""就可以,有的时候则必须使用equals方法来进行判断??? 于是我开始在网上查阅资料,几经无果,我开始阅读源码,一段时间后我才知道:原来Integer还有…

global关键字

global关键字 如果你想在局部作用域中修改全局变量,可以基于global关键字进行实现 默认情况下,在局部变量作用域只能对全局变量进行: 读取和修改内部元素(可变类型),无法对全局变量进行重新赋值 读取 …