设计模式——单例模式(Singleton Pattern)

       概述

        单例模式确保一个类只有一个实例,而且自行实例化并向整个系统提供整个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。单例模式有三个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

       单例模式结构图中只包含一个单例角色:

Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。

       一个简单的单例模式如下:

以上单例无法保证线程安全,当多个线程同时调用时,由于实例尚未创建成功,仍为null,因此new LogManager()会被执行多次,最终导致创建多个实例对象,这违背了单例模式的初衷,也导致系统运行发生错误。为了解决该问题,有以下几种方式:

实现方式
饿汉式单例类

当类被加载时,静态变量logManager就会被初始化,单例类的唯一实例将被创建。无论多个线程调用,都保证了实例的唯一性,确保线程安全。

懒汉式单例类

懒汉式单例在第一次调用getInstance()方法时实例化,在类加载时并不自行实例化,这种技术又称为延迟加载(Lazy Load)技术,即需要的时候再加载实例,为了避免多个线程同时调用getInstance()方法,我们可以使用关键字synchronized,代码如下所示:

问题貌似得以解决,事实并非如此。如果使用以上代码来实现单例,还是会存在单例对象不唯一。原因如下:

      假如在某一瞬间线程A和线程B都在调用getInstance()方法,此时instance对象为null值,均能通过instance == null的判断。由于实现了synchronized加锁机制,线程A进入synchronized锁定的代码中执行实例创建代码,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized锁定代码。但当A执行完毕时,线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象,违背单例模式的设计思想,因此需要进行进一步改进,在synchronized中再进行一次(instance == null)判断,这种方式称为双重检查锁定(Double-Check Locking)。使用双重检查锁定实现的懒汉式单例类完整代码如下所示:

需要注意的是,如果使用双重检查锁定来实现懒汉式单例类,需要在静态成员变量instance之前增加修饰符volatile,被volatile修饰的成员变量可以确保多个线程都能够正确处理,且该代码只能在JDK 1.5及以上版本中才能正确执行。由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,因此即使使用双重检查锁定来实现单例模式也不是一种完美的实现方式。

饿汉式单例类与懒汉式单例类比较

饿汉式单例类在类被加载时就将自己实例化,它的优点在于无须考虑多线程访问问题,可以确保实例的唯一性;从调用速度和反应时间角度来讲,由于单例对象一开始就得以创建,因此要优于懒汉式单例。但是无论系统在运行时是否需要使用该单例对象,由于在类加载时该对象就需要创建,因此从资源利用效率角度来讲,饿汉式单例不及懒汉式单例,而且在系统加载时由于需要创建饿汉式单例对象,加载时间可能会比较长。

      懒汉式单例类在第一次使用时创建,无须一直占用系统资源,实现了延迟加载,但是必须处理好多个线程同时访问的问题,特别是当单例类作为资源控制器,在实例化时必然涉及资源初始化,而资源初始化很有可能耗费大量时间,这意味着出现多线程同时首次引用此类的机率变得较大,需要通过双重检查锁定等机制进行控制,这将导致系统性能受到一定影响。

        有没有一种方法,能够将两种单例的缺点都克服,而将两者的优点合二为一呢?答案是:Yes!下面我们来学习这种更好的被称之为Initialization Demand Holder (IoDH)的技术。在IoDH中,我们在单例类中增加一个静态(static)内部类,在该内部类中创建单例对象,再将该单例对象通过getInstance()方法返回给外部使用,实现代码如下所示:

由于静态单例对象没有作为LogManager的成员变量直接实例化,因此类加载时不会实例化LogManager,第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。

      通过使用IoDH,我们既可以实现延迟加载,又可以保证线程安全,不影响系统性能,不失为一种最好的Java语言单例模式实现方式(其缺点是与编程语言本身的特性相关,很多面向对象语言不支持IoDH)。

单例模式被破坏
克隆、反射和反序列化会对传统的单例模式破坏。

在其他创建型设计模式的学习中,我们已经了解,除了直接通过new和使用工厂来创建对象以外,还可以通过克隆、反射和反序列化等方式来创建对象。但是用这些方式来创建对象时有可能会导致单例对象的不唯一,如何解决这些问题呢?

        (1) 为了防止客户端使用克隆方法来创建对象,单例类不能实现Cloneable接口,即不能支持clone()方法。

        (2) 由于反射可以获取到类的构造函数,包括私有构造函数,因此反射可以生成新的对象。【如何解决:采用枚举实现】

        采用一些传统的实现方法都不能避免客户端通过反射来创建新对象,此时,我们可以通过枚举单例对象的方式来解决该问题。

        (3) 在原型模式中,我们可以通过反序列化实现深克隆,反序列化也会生成新的对象。具体来说就是每调用一次readObject()方法,都将会返回一个新建的实例对象,这个新建的实例对象不同于类在初始化时创建的实例对象。

        那么,如何防止反序列化创建对象呢?解决方法一是类不能实现Serializable接口,即不允许该类支持序列化,这将导致类的应用受限制(有时候我们还是需要对一个对象进行持久化处理);解决方法二就是本文将要详细介绍的枚举实现。

简单实现

      下面我们分析如何使用枚举Enum来实现单例模式。

      Google 首席 Java 架构师、《Effective Java》一书作者、Java集合框架的开创者Joshua Bloch在Effective Java一书中提到:

    单元素的枚举类型已经成为实现Singleton的最佳方法。在这种实现方式中,既可以避免多线程同步问题;还可以防止通过反射和反序列化来重新创建新的对象。在很多优秀的开源代码中,我们经常可以看到使用枚举方式来实现的单例类。

        下面我们来详细分析如何使用枚举实现单例模式。

        枚举是在JDK1.5以及以后版本中增加的一个“语法糖”,它主要用于维护一些实例对象固定的类。例如一年有四个季节,就可以将季节定义为一个枚举类型,然后在其中定义春、夏、秋、冬四个季节的枚举类型的实例对象。 按照Java语言的命名规范,通常,枚举的实例对象全部采用大写字母定义,这一点与Java里面的常量是相同的。

        首先我们来看一下最简单的单例模式枚举实现。

        因为Java虚拟机会保证枚举对象的唯一性,因此每一个枚举类型和定义的枚举变量在JVM中都是唯一的。

        最简单的实现方式如下代码所示:

在main()函数中,我们通过Singleton.INSTANCE获得两个对象s1和s2,然后比较s1是否等于s2,最后输出true,说明s1和s2是同一个对象,所得到的对象具有唯一性。由于单例模式的枚举实现代码比较简单,而且又可以利用枚举的特性来解决线程安全和单一实例的问题,还可以防止反射和反序列化对单例的破坏,因此在很多书和文章中都强烈推荐将该方法作为单例模式的最佳实现方法

单例模式总结

单例模式作为一种目标明确、结构简单、理解容易的设计模式,在软件开发中使用频率相当高,在很多应用软件和框架中都得以广泛应用。

主要优点

       单例模式的主要优点如下:

       (1) 单例模式提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。

       (2) 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。

       (3) 允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例单例对象共享过多有损性能的问题。

主要缺点

       单例模式的主要缺点如下:

       (1) 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。

       (2) 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。

       (3) 现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。

适用场景

       在以下情况下可以考虑使用单例模式:

       (1) 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。如:ServletContext,ServletConfig,ApplicationContext,DBPool,ThreadPool等。

       (2) 客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。

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

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

相关文章

Java---线程讲解(二)

文章目录 1. Runnable接口2. 卖票案例3. 同步代码块解决数据安全问题4. 同步方法解决数据安全问题5. 线程安全的类6. Lock锁 1. Runnable接口 1. 创建线程的另一种方法是声明一个实现Runnable接口的类,之后重写run()方法,然后可以分配类的实例&#xff0…

基于remix+metamask+ganache的智能合约部署调用

在我们部署合约时为了让它更接近真实区块链去中心化体验,我们需要调用小狐狸(Metamask)来进行真实交易,而metamask里没有内置虚拟测试币,我们需要进行调用Ganache来添加带有虚拟测试币的账号。以上就是三者的关系&…

从 MLOps 到 LMOps 的关键技术嬗变

本文整理自 2023 年 9 月 3 日 QCon 全球软件开发大会 2023 北京站 —— 从 MLOps 到 LMOps 分论坛的同名主题演讲。 本次分享的内容结构如下: 从 MLOps 到 LMOps; MLOps 概述、挑战与解决方案; LMOps 实施挑战与关键技术(大模…

[FPGA 学习记录] 快速开发的法宝——IP核

快速开发的法宝——IP核 文章目录 1 IP 核是什么2 为什么要使用 IP 核3 IP 核的存在形式4 IP 核的缺点5 Quartus II 软件下 IP 核的调用6 Altera IP 核的分类 在本小节当中,我们来学习一下 IP 核的相关知识。 IP 核在 FPGA 开发当中应用十分广泛,它被称为…

使用Golang构建高性能网络爬虫

目录 一、Golang的特点 二、构建网络爬虫的步骤 三、关键技术和注意事项 使用协程进行并发处理 使用通道进行协程间的通信 合理控制并发数和处理速度 遵守网站使用协议和法律法规 防止被网站封禁或限制访问 优化网页解析和数据处理 异常处理和错误处理 日志记录和监控…

vmware虚拟机17 安装macos14过程及问题处理亲测

前期准备 1、可引导可虚拟机安装的macOS Sonoma 14 ISO镜像安装文件 我找到得地址,下载自行解决啦 2、VMware虚拟机应用软件 官网下载就好,搜个码搞定 3、解锁工具macOS Unlocker 开始安装: 1、打开VMware软件,新建一个系统…

Bert-vits2新版本V2.1英文模型本地训练以及中英文混合推理(mix)

中英文混合输出是文本转语音(TTS)项目中很常见的需求场景,尤其在技术文章或者技术视频领域里,其中文文本中一定会夹杂着海量的英文单词,我们当然不希望AI口播只会念中文,Bert-vits2老版本(2.0以下版本)并不支持英文训练和推理&…

完整方案开放下载!详解中国移动《通信网络中量子计算应用研究报告》

8月30日,中国移动在第四届科技周暨战略性新兴产业共创发展大会上重磅发布了《通信网络中量子计算应用研究报告》。 玻色量子作为中国移动在光量子计算领域的唯一一家合作企业兼战投企业,在量子计算应用于通信行业达成了深入合作,并在5G天线多…

干货分享|300平米A级机房设计方案

本方案中XXX计算机中心机房建设工程,是XXX的数据中心,机房位于建筑的X层,计算机机房面积300㎡。采购设备以及装修工艺主要用于XXX所属计算机机房装修工程。 考虑到中心机房投资大、使用周期长,而业主业务发展快,现代技…

空间地图GIS基础

关注微信公众号掌握更多技术动态 --------------------------------------------------------------- 一、GIS基本概念 地理信息系统(Geographic Informaiton System, GIS)是一个可以建立、浏览、查询、分析地理空间数据的软件系统,其功能小…

【RHCE】openlab搭建web网站

网站需求: 1、基于域名 www.openlab.com 可以访问网站内容为 welcome to openlab!!! 增加映射 [rootlocalhost ~]# vim /etc/hosts 创建网页 [rootlocalhost ~]# mkdir -p /www/openlab [rootlocalhost ~]# echo welcome to openlab > /www/openlab/index.h…

利用法线贴图渲染逼真的3D老虎模型

在线工具推荐: 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 当谈到游戏角色的3D模型风格时,有几种不同的风格&#xf…

3-Mybatis

文章目录 Mybatis概述什么是Mybatis?Mybatis导入知识补充数据持久化持久层 第一个Mybatis程序:数据的增删改查查创建环境编写代码1、目录结构2、核心配置文件:resources/mybatis-config.xml3、mybatis工具类:Utils/MybatisUtils4、…

ALNS的MDP模型| 还没整理完12-08

有好几篇论文已经这样做了,先摆出一篇,然后再慢慢更新 第一篇 该篇论文提出了一种称为深增强ALNS(DR-ALNS)的方法,它利用DRL选择最有效的破坏和修复运营商,配置破坏严重性参数施加在破坏算子上&#xff0c…

第二十一章网络通信总结

21.1 网络程序设计基础 Java网络程序设计基础涉及使用Java编程语言创建网络应用程序。这通常涉及到使用Java的网络API,如java.net包,以建立客户端和服务器之间的通信。 基本步骤包括: 1.创建服务器: 使用ServerSocket类创建服务…

常见的中间件--消息队列中间件测试点

最近刷题,看到了有问中间件的题目,于是整理了一些中间件的知识,大多是在小破站上的笔记,仅供大家参考~ 主要分为七个部分来分享: 一、常见的中间件 二、什么是队列? 三、常见消息队列MQ的比较 四、队列…

12_企业架构之Tomcat部署使用

Tomcat 学习目标和内容 1、能够描述Tomcat的使用场景 2、能够简单描述Tomcat的工作原理 3、能够实现部署安装Tomcat 4、能够实现配置Tomcat的service服务和自启动 5、能够实现Tomcat的Host的配置 6、能够实现Nginx反向代理Tomcat 7、能够实现Nginx负载均衡到Tomcat 一、Tomcat介…

linux的定时任务Corntab

安装crontab # yum安装crontab yum install -y crontab# 开机自启crond服务并现在启动 systemctl enable --now crondcron系统任务调度 系统任务调度: 系统周期性所要执行的工作,比如写缓存数据到硬盘、日志清理等。 在/etc/crontab文件,这…

机器学习之全面了解回归学习器

我们将和大家一起探讨机器学习与数据科学的主题。 本文主要讨论大家针对回归学习器提出的问题。我将概要介绍,然后探讨以下五个问题: 1. 能否将回归学习器用于时序数据? 2. 该如何缩短训练时间? 3. 该如何解释不同模型的结果和…

No suitable driver found for jdbc:mysql://localhost:3306(2023/12/7更新)

有两种情况: 压根没安装下载了但没设为库或方法不对 大多数为第一种情况: 一. 下载jdbc 打开网址选择一个版本进行下载 https://nowjava.com/jar/version/mysql/mysql-connector-java.html 二.安装jdbc 在项目里建一个lib文件夹 在把之前下载的jar文…