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

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

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

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

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

单例的创建又有饿汉式、懒汉式、双重检测,甚至利用 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)

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • SQL,连结多行的字串并去除重复
  • FreeRTOS 快速入门(四)之队列
  • 发文首选:KAN用于图像处理!效果炸裂好
  • Spring Cloud Gateway动态路由及路由插件实现方案
  • Isaac Sim仿真平台学习(2)基础知识
  • ‌前端列表展示1000条大量数据时,后端通常需要进行一定的处理。‌
  • 视频美颜SDK与直播美颜工具的架构设计与性能优化
  • STM32之点亮LED灯
  • 大数据量实现滚动分页-vue3+element-plus实现方式
  • docker升级docker pull mysql:5.7.37异常
  • C++ 11---lambda表达式与包装器
  • 整体思想以及取模
  • Spring @Async注解【总结记录】
  • 点对点专线的带宽管理和控制功能解析
  • 【AI趋势9】开源普惠
  • 【翻译】babel对TC39装饰器草案的实现
  • 【跃迁之路】【733天】程序员高效学习方法论探索系列(实验阶段490-2019.2.23)...
  • canvas 高仿 Apple Watch 表盘
  • js继承的实现方法
  • Node.js 新计划:使用 V8 snapshot 将启动速度提升 8 倍
  • SpingCloudBus整合RabbitMQ
  • sublime配置文件
  • ⭐ Unity 开发bug —— 打包后shader失效或者bug (我这里用Shader做两张图片的合并发现了问题)
  • vue从入门到进阶:计算属性computed与侦听器watch(三)
  • 闭包--闭包作用之保存(一)
  • 从tcpdump抓包看TCP/IP协议
  • 前端技术周刊 2019-02-11 Serverless
  • 悄悄地说一个bug
  • 区块链共识机制优缺点对比都是什么
  • 使用 Docker 部署 Spring Boot项目
  • 手机端车牌号码键盘的vue组件
  • 通信类
  • 微信小程序设置上一页数据
  • 异常机制详解
  • 正则表达式
  • No resource identifier found for attribute,RxJava之zip操作符
  • AI算硅基生命吗,为什么?
  • puppet连载22:define用法
  • Redis4.x新特性 -- 萌萌的MEMORY DOCTOR
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • ​iOS安全加固方法及实现
  • ​如何防止网络攻击?
  • #pragma预处理命令
  • $.extend({},旧的,新的);合并对象,后面的覆盖前面的
  • (14)目标检测_SSD训练代码基于pytorch搭建代码
  • (C++)八皇后问题
  • (delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)
  • (力扣)循环队列的实现与详解(C语言)
  • (十五)Flask覆写wsgi_app函数实现自定义中间件
  • (十一)图像的罗伯特梯度锐化
  • (算法设计与分析)第一章算法概述-习题
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战
  • (转)setTimeout 和 setInterval 的区别
  • (转)全文检索技术学习(三)——Lucene支持中文分词
  • (自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载