重修设计模式-创建型-单例模式

重修设计模式-创建型-单例模式

一个类只允许创建一个对象(或实例),那这个类就是一个单例类,这种模式叫做单例设计模式。

单例的主要使用场景有两个,一是使用单例控制全局的资源访问,也就是用单例封装一些工具类,比如日志写入工具;二是有些数据在系统中应该只保存一份,比如应用程序的用户登录信息,需要用单例设计一个用户信息管理类。

单例的创建又有饿汉式、懒汉式、双重检测,甚至利用 JVM 虚拟机来创建单例对象,下面介绍一下这些创建方式。

一、饿汉式(着急吃)

在类加载时就将类的实例对象创建好了,是一种提前初始化的行为。

因为实例对象会占用内存或其他资源,所以这种提前初始有些人认为是一种资源浪费,应该在使用时再进行。但世事无绝对,如果你是后端开发人员,在接口请求到来时再去进行初始化操作,就会导致请求的响应时间变长甚至超时;如果你是前端开发,由于内存和资源的宝贵,则完全可以将单例初始化平摊到后续的用户操作中。

这种方式有优点也有缺点,具体使用还要根据使用场景,Kotlin 中的 Object 关键字就可以生命一个单例的类,实例默认就是饿汉式创建的:

object SingleClass {fun doSoming() {println("doSoming...")}
}//使用:
SingleClass.doSoming()

二、懒汉式(不着急)

懒汉式指的是将实例对象的创建放到使用时,也就是延迟初始化。

class SingleInstance2 private constructor() {companion object {private lateinit var sInstance: SingleInstance2@Synchronizedfun getInstance(): SingleInstance2 {if (!sInstance::isInitialized) { //判断是否已经初始化sInstance = SingleInstance2()}return sInstance}}fun foo() {println("do...")}
}

这里用 Java 举例,会将实例初始化过程使用 synchronized 锁住,但加锁是非常损耗性能的操作,所以非常不推荐这样去写,更常见的写法应该是下面的双重检测。

三、双重检测

其实也是懒汉式思想,只是优化了实现方式。双重检测的标准化代码如下:

//懒汉式-双重检测
class SingleInstance3 private constructor() {companion object {@Volatile   //防止指令重排private var sInstance: SingleInstance3? = nullfun getInstance(): SingleInstance3 {if (sInstance == null) {    //第一次检测,为了避免加锁synchronized(this) {if (sInstance == null) {    //第二次检测,为了防止并发情况重复初始化sInstance = SingleInstance3()}}}return sInstance!!}}fun foo() {println("do...")}
}

第一次检测是在实例已经创建出来后,避免加锁逻辑;

第二次检测是为了防止多线程锁竞争时,导致的重复初始化。比如线程A通过了第一次检测,此时切换到线程B,由于 sInstance 还没被赋值,线程B也通过了第一次检测,此时无论哪个线程进入加锁代码,另一个线程都会等待锁释放后再进入加锁代码,这时就会导致重复初始化。

volatile 关键字则是为了防止指令重排导致的没有完整进行初始化的对象被使用场景。这里就要介绍一下实例的创建流程,分为三步:

  1. 分配内存(JVM 堆,为实例变量分配空间和赋予默认值(零值))
  2. 初始化对象(包括实例变量初始化(赋予真正的值),示例代码块初始化,构造函数初始化)
  3. 将内存地址指向引用对象本身

正常顺序是123,如果指令重排序后是132,在执行到3时,另一个线程进入获取对象,这时虽然 sInstance 不为空,但还未执行初始化操作,就会导致获取到的对象是未初始化的对象。

双重检测的代码是非常模板化的,可以进一步封装这些代码(Kotlin语言):

/***  description : Double Check 方式单例封装(由伴生对象继承)*/
abstract class BaseSingleton<out T> {@Volatileprivate var instance: T? = nullprotected abstract fun creator(): Tfun getInstance(): T = instance ?: synchronized(this) {instance ?: creator().also { instance = it }}
}

使用时用伴生对象去继承:

class SingleInstance6 private constructor() {companion object: BaseSingleton<SingleInstance6>() {override fun creator(): SingleInstance6 {return SingleInstance6()}}
}

四、利用 JVM 规范初始化单例对象

  1. 静态内部类

    public class SingleInstance4 {private SingleInstance4() {}private static class SingletonHolder{private static final SingleInstance4 instance = new SingleInstance4();}public static SingleInstance4 getInstance() {return SingletonHolder.instance;}public void foo() {System.out.println("do...");}
    }
    

    这种方式利用的就是 JVM 的类加载时机,SingletonHolder 是一个静态内部类,当外部类 SingleInstance4 被加载的时候,并不会创建 SingletonHolder 实例对象。只有当调用 getInstance() 方法时,SingletonHolder 才会被加载,这个时候才会创建 instance。instance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。

    1. 枚举
    public enum SingleInstance5 {INSTANCE;public void foo() {System.out.println("do...");}
    }
    

    通过 Java 枚举类型本身的特性,可以保证实例创建的线程安全性和实例的唯一性。

总结

一个类只有一个实例对象就是单例模式。

实现单例需要注意的点:

  1. 构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例;
  2. 考虑对象创建时的线程安全问题;(Double Check)
  3. 考虑是否支持延迟加载;(懒汉式)
  4. 考虑 getInstance() 性能是否高(是否加锁🔒,Double Check)

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

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

相关文章

基于C# winform部署图像动漫化AnimeGANv2部署onnx模型

【界面截图】 【效果演示】 【部分实现代码】 using System; using System.Diagnostics; using System.Windows.Forms; using OpenCvSharp;namespace FIRC {public partial class Form1 : Form{Mat src null;public Form1(){InitializeComponent();}private void button1_Cli…

html+css+js网页设计 天猫首页

htmlcssjs网页设计 天猫首页 网页作品代码简单&#xff0c;可使用任意HTML编辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码 1&#xff0c;访问…

git本地仓库同步到远程仓库

整个过程分为如下几步&#xff1a; 1、本地仓库的创建 2、远程仓库的创建 3、远程仓库添加key 4、同步本地仓库到远程仓库 1、本地仓库的创建&#xff1a; 使用如下代码创建本地仓库&#xff1a; echo "# test" >> README.md git init git add README.md …

用户增长:策略与实践,驱动SaaS企业持续繁荣

在当今这个数字化时代&#xff0c;用户增长已成为所有行业&#xff0c;尤其是SaaS&#xff08;Software as a Service&#xff0c;软件即服务&#xff09;企业生存与发展的核心驱动力。用户增长不仅关乎市场份额的扩大&#xff0c;更是企业价值实现和持续盈利的基石。那么&…

【计算机网络】网络版本计算器

此前我们关于TCP协议一直写的都是直接recv或者read&#xff0c;有了字节流的概念后&#xff0c;我们知道这样直接读可能会出错&#xff0c;所以我们如何进行分割完整报文&#xff1f;这就需要报头来解决了&#xff01; 但当前我们先不谈这个话题&#xff0c;先从头开始。 将会…

【秋招笔试】8.18大疆秋招(第一套)-后端岗

🍭 大家好这里是 春秋招笔试突围,一起备战大厂笔试 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 编程一对一辅导 ✨ 本系列打算持续跟新 春秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 和 手里的小花花🌸 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 🍒 本专栏已收…

Springboot发邮件功能如何实现?详细步骤?

Springboot发邮件配置指南&#xff1f;如何集成Spring Mail发邮件&#xff1f; 无论是用户注册、密码重置还是重要通知的发送&#xff0c;邮件都是不可或缺的沟通方式。Springboot作为一个流行的Java开发框架&#xff0c;提供了简洁易用的方式来实现邮件功能。AokSend将详细探…

音频转换器有哪些?一键转换,轻松享受

暑假里&#xff0c;你是否也沉浸在激情四溢的演唱会中&#xff0c;用手机记录下了那些难忘的现场音频&#xff1f; 但回到家中&#xff0c;想要将这些珍贵的现场记忆从手机迁移到电脑上永久保存时&#xff0c;却遇到了格式不兼容的难题。 别担心&#xff0c;今天我们就要解决…

基于Python的机器学习系列(8):Newton Raphson逻辑回归

在本篇博文中&#xff0c;我们将探讨一种比传统梯度下降更高效的优化方法——Newton Raphson方法&#xff0c;并学习如何在逻辑回归中应用它。Newton Raphson方法通过利用二阶导数的曲率信息&#xff0c;快速地找到使代价函数最小化的参数。尽管这种方法在处理较小规模的数据集…

04 C++语言---数据类型

C中数据分为两种&#xff0c;一种是可以被修改的数据&#xff0c;这种数据一般被成为变量&#xff0c;还有一种数据是常量。 变量&#xff1a;变量在程序中的使用场景有很多&#xff0c;一般常用的数据类型都属于常量。例如 int a&#xff1b;等。 常量&#xff1a;常量是在程…

前端项目重新打包部署后如何通知用户更新

前端项目重新打包部署后如何通知用户更新 前端项目重新打包部署后如何通知用户更新常用的webSocket解决方案纯前端方案路由拦截多线程main.ts中 创建多线程多线程逻辑处理 前端项目重新打包部署后如何通知用户更新 前端项目重新打包部署后&#xff0c;由于用户没及时更新页面&…

关于python的Django项目性能优化

CPU—— Python编写的Django程序在服务器上消耗CPU的主要原因包括频繁的IO操作、大量的计算任务、以及第三方库的性能问题。‌ ‌频繁的IO操作‌&#xff1a;当Python程序频繁进行IO操作&#xff0c;如读写文件、网络请求等&#xff0c;会导致CPU占用过高。这是因为IO操作通常是…

什么是OpenTiny?

OpenTiny 是一套企业级的 Web 前端开发解决方案&#xff0c;提供跨端、跨框架的 UI 组件库和低代码引擎&#xff0c;帮助开发者高效构建 Web 应用 。企业运用开发中&#xff0c;可以利用 OpenTiny 的以下核心组件和优势&#xff1a; TinyVue 组件库&#xff1a;一个丰富的组件库…

python初级爬虫实战:我是怎么用python下载音乐的

今天分享的内容是如何使用python下载歌曲和歌词信息&#xff0c;文章涉及内容主要为了帮助大家学习python技能&#xff0c;请大家合规合理使用。 如果你正在学习Python爬虫&#xff0c;但是找不到方向的话可以试试我这一份学习方法和籽料呀&#xff01;点击 领取&#xff08;不…

汽车IVI中控OS Linux driver开发实操(二十四):I2C设备驱动的编写

概述: 在Linux驱动中I2C系统中主要包含以下几个成员: I2C adapter(即I2C适配器,用来控制各种I2C从设备,其驱动需要完成对适配器的完整描述,最主要的工作是需要完成i2c_algorithm结构体。这个结构体包含了此I2C控制器的数据传输具体实现,以及对外上报此设备所支持的功…

0.91寸OLED迷你音频频谱

一、简介 音频频谱在最小0.91寸OLED 屏幕上显示&#xff0c;小巧玲珑 二、应用场景 本模块为音频频谱显示模块&#xff0c;用来获取声音频谱并展示频谱&#xff0c;跟随音乐声音律动 三、产品概述 基于主控芯片设计的将声音采集分析频谱&#xff0c;显示到0.91寸OLED的功能…

我们如何将数据输入到神经网络中?

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 下面我拿识别美女的例子来给大家介绍如何将美女的图片数据输入到神经网络中。 此例中&#xff0c;待输入的数据是一张图像。为了存储图像…

Java中String类的经典问题、错误认知以及归纳总结

在学习过程中对String类的理解反复刷新&#xff0c;以此文记之&#xff0c;做归纳总结&#xff0c;也适合新手避坑。 以实用性考虑&#xff0c;环境为Java 8 以及 之后版本。 String类相比其它类特殊的地方在于有一个字符串常量池(StringTable)&#xff0c;里面存着字面量的引…

Hackademic.RTB1靶场实战【超详细】

靶机下载链接&#xff1a;https://download.vulnhub.com/hackademic/Hackademic.RTB1.zip 一、主机探测和端口扫描 nmap 192.168.121.0/24 ip:192.168.121.196 端口:22、80 二、访问80端口 发现target可点击 点击后跳转&#xff0c;页面提示目标是读取到 key.txt 文件 fin…

Enhancing Octree-Based Context Models for Point Cloud Geometry Compression 论文笔记

1. 论文基本信息 发布于&#xff1a; IEEE SPL 2024 2. 创新点 分析了基于 one-hot 编码的交叉熵损失函数为什么不能准确衡量标签与预测概率分布之间的差异。介绍了 ACNP 模块&#xff0c;该模块通过预测占用的子节点数量来增强上下文模型的表现。实验证明了ACNP模块在基于八…