【设计模式】单例模式、“多例模式”的实现以及对单例的一些思考

news2025/2/22 22:05:47

文章目录

  • 1.概述
  • 2.单例模式实现代码
    • 2.1.饿汉式单例
    • 2.2.懒汉式单例
    • 2.3.双检锁单例
    • 2.4.静态内部类单例
    • 2.5.枚举单例
  • 3.对单例的一些思考
    • 3.1.是否需要严格的禁止单例被破坏?
    • 3.2.懒汉式真的比饿汉式更佳吗?
    • 3.3.单例存在的问题
  • 4.其他作用范围的单例模式
    • 4.1.线程内的单例
    • 4.2.进程间的单例
  • 5.“多例模式”
  • 6.总结

1.概述

单例模式是设计模式中最简单的一种,对于很多人来说,单例模式也是其接触的第一种设计模式,当然,我也不例外。这种设计模式在学习、面试、工作的过程中广泛传播,相信不少人在面试时遇到过这样的问题:“说说你最熟悉的集中设计模式”,第一个脱口而出的就是单例模式。

所谓的单例模式,就是在一定的作用范围内保证只有一个实例,这种模式,简单,但是想要使用好它,还需要学习一下它延伸出来的其他知识点,本篇博文就对单例模式做一下简单的整理,主要会包含以下几部分内容:

  • 单例模式的代码如何编写?
  • 是否需要严格的禁止单例被破坏?
  • 饿汉式和懒汉式应该如何选择?
  • 单例模式存在什么问题?
  • 线程内单例和进程间单例如何实现?
  • 什么叫做“多例模式”?

2.单例模式实现代码

单例模式的实现代码很多,下面会例举一些常见的方式。

Java中,单例模式的作用范围一般情况下指的是当前的Java进程,也就是进程内的对象保证唯一(当然还有线程内、进程之间的单例,下面会提到),所以我们需要保证实例只会被初始化一次,如何保证呢?

2.1.饿汉式单例

一个简单的做法,就是私有化构造方法,也就是不让外部的客户端对象来调用new方法,创建新的实例,而是在项目启动时,由单例类自行初始化,这就是饿汉式单例

/**
 * 饿汉式单例
 */
public class HungrySingleton {

    private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();

    private HungrySingleton() {}

    public static HungrySingleton getInstance() {
        return HUNGRY_SINGLETON;
    }
}

2.2.懒汉式单例

如果不想再项目启动时初始化,而是在使用的时候再初始化对象,可以将对象的创建放到getInstance方法中,这种方式叫做懒汉式单例。这种方式在多线程的情况下会有线程安全问题,需要在创建对象时加锁。

/**
 * 懒汉式单例
 */
public class LazySingleton {

    private static volatile LazySingleton lazySingleton;

    private LazySingleton() {}

    public static synchronized LazySingleton getInstance() {
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

2.3.双检锁单例

在方法加的是类锁,一次只有一个线程可以获取到单例对象,为了提高获取对象的效率,取消在方法上的类锁,转而只给创建对象的那一行代码加锁。但是在并发的情况下,多个线程同时进入getInstance方法,都可以通过lazySingleton == null的判断,并在加锁那一行排队,每个线程都会创建一个新的对象,所以,我们需要在锁里面再判断一次对象是否创建。
这种在加锁的代码前后都进行一次相同判断的做法,我们叫做双重检查锁,简称:双检锁

/**
 * 双检锁单例
 */
public class LazySingleton {

    private static volatile LazySingleton lazySingleton;

    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (lazySingleton == null) {
            synchronized (LazySingleton.class) {
                if (lazySingleton == null) {
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }
}

2.4.静态内部类单例

上面的懒汉式是为了保证懒加载的同时,又不能有线程安全问题,我们采用了加锁的方式,那么不加锁行不行呢?
当然是可以的,我们采用静态内部类在受访问时才会初始化的特性,来实现懒加载。

/**
 * 静态内部类单例
 */
public class StaticClassSingleton {

    private StaticClassSingleton() {
    }

    public static StaticClassSingleton getInstance() {
        return InnerStaticClassSingleton.STATIC_CLASS_SINGLETON;
    }

    private static class InnerStaticClassSingleton {
        private static final StaticClassSingleton STATIC_CLASS_SINGLETON = new StaticClassSingleton();
    }
}

2.5.枚举单例

Java中的枚举是天然的单例模式,这种方式实现最简单,而且不会有线程安全问题,也能避免通过反射或者反序列化创建新的对象。
下面代码中的INSTANCE就是单例对象了。

/**
 * 枚举单例
 */
public enum EnumSingleton {
    INSTANCE;
}

3.对单例的一些思考

3.1.是否需要严格的禁止单例被破坏?

在上面的代码例子中,我们采用的方式是私有化构造方法方法来避免外部对象new出新的单例对象,但这种方式并不能完全避免创建出新的对象。其实上面的枚举单例中已经提到了,可以通过反射、反序列化等方式,创建出新的对象。

在考虑如何避免通过反射、反序列化创建对象,可以先思考一下,有没有必要去避免?

还是回到最初那个点,我们用了这么多方式来实现单例模式,最终的目的就是为了在进程内的作用范围内只有一个实例。而在代码的开发过程中,我们做好规范和约束,以人为的方式来控制对象的创建数量,哪怕没有私有化构造方法方法也能保证单例。而我们私有化构造方法,更多的是给开发者做出一个提示,这个类是个单例类,并且防止一定的误操作。
可以想象一下,我们要完全将各类创建和初始化的逻辑都“封闭”掉,代码会臃肿到什么地步,而这部分“封闭”的逻辑在业务开发中我们完全使用不到,所以更建议以一种“约定由于配置”的方式来处理单例的创建问题。

综上,做到私有化构造方法这一步就够了,不需要过度开发。

3.2.懒汉式真的比饿汉式更佳吗?

懒汉式主要是为了做懒加载,当单例对象没有使用的时候就不创建和初始化,特别是初始化是需要加载的资源比较多、比较耗时的时候,用懒加载可以加快项目启动的速度,同时又能减少系统的资源浪费。
但是从另一个角度讲,如果不是在启动的时候初始化,那就是在客户端调用的时候初始化,想象一下一个高并发的互联网项目,如果在客户端调用的时候再做耗时的初始化动作,就可能造成接口的请求时间过长,接口超时等,会影响一批用户的使用体验。
另外,如果初始化的过程中存在一些异常情况,我们应该让问题在项目启动时就暴露出来,及时修复,而不是在用户使用的时候才暴露问题。

综上,在业务开发中或许饿汉式单例是更好的选择。

3.3.单例存在的问题

单例模式编写和使用都很简单,但是它也存在一些问题,例如:

  • 面向对象支持不好:单例模式在作用范围内只有一个实例,那就无法通过创建更多的实例来使用面向对象的抽象、继承、多态等特性,更像是一种面向过程的写法。
  • 违反开闭原则:无法拓展,每一次迭代都需要修改原有的代码。
  • 违反单一职责:单例模式既创建对象、又管理对象,职责模糊,可能会导致代码变得复杂。

综上,单例模式存在一定的问题,如果存在拓展的需求就尽可能的避免使用单例模式。

但如果在不需要大量的拓展,又没有业务间的复杂依赖关系,使用单例模式就比较简洁方便也不失为一种选择,例如各种无状态的工具类。

4.其他作用范围的单例模式

4.1.线程内的单例

即单例对象在线程内时唯一的,线程之间不是唯一的,我们开发中有一种很常见的情况:线程局部变量,一般是通过ThreadLocal来做的。
实现原理也比较简单,其实就是使用一个全局的Map来保存对象,以线程对象threadkey,以需要保存的单例对象为value,这样就保证了一个线程只对应一个对象。
在业务流程中的用户登录信息,往往就是保存在ThreadLocal中的,另外PageHelper这个著名的工具类也是通过ThreadLocal来实现的。


如果想了解ThreadLocal的使用方式,可以参考我的另一篇博客《【并发编程】(九)线程安全的代码及ThreadLocal的使用》
如果想了解它详细的实现原理,可以参考《【并发编程】(十)线程局部变量——ThreadLocal原理详解》

4.2.进程间的单例

进程间的单例,更常用的一种说法分布式环境中的单例,这类需求我们使用的也比较多,其实现原理也比较简单,就是将单例对象通过序列化的方式存储在一个多个服务都会共同访问的存储区域中,例如一个共享的文件中、一些分布式的中间件中,例如rediszk等等,而最常见的当然就是分布式锁
我们只需要为单例对象创建出一个唯一标识,在每个服务中判断唯一标识是否存在即可。

5.“多例模式”

多例模式是单例模式中的一种特例,即可以在一定数量范围内创建类的多个实例,还有一层理解就是不同类型的对象可以创建多个,想通类型的对象只能创建一个,后者的概念使用的更多。

以日志打印为例,我们引入Slf4J后通过下面的方式获得一个日志对象:

private Logger logger = LoggerFactory.getLogger(xxx.class);

这里获取的logger如果后面的class对象相同,获取的就是同一个对象,这种方式更像是工厂模式,在代码中看到的也是工厂模式,如下图:
我们进入这个工厂模式的方法后,可以看到下面的代码:
在这里插入图片描述
这里就非常明显了,这就是一种单例模式的创建方式,通过一个Map将单例对象管理起来,如果Map中有就直接返回,如果没有就创建一个并放入到Map中,这里的对象都是logger对象,只是使用日志的类不一样,这就是多例模式的一种体现。

另外,在Spring中如果配置的bean是单例的,其创建方式也与这种方式类似。

6.总结

本来主要讲述了以下几个点:

  • 单例模式的编写方式
    饿汉式、懒汉式、静态内部类、枚举
  • 是否需要严格的禁止单例被破坏:
    没有必要写的太严格,可以通过规范的方式来约束
  • 饿汉式和懒汉式应该如何选择:
    让耗时操作提前初始化,让问题提早暴露,及时修改,而不是让用户去发现
  • 单例模式存在什么问题
    没有面向对象,拓展性差
  • 线程内单例和进程间单例如何实现
    线程内单例通过线程局部变量来实现,进程间的单例通过共享的存储区域来实现
  • 什么叫做“多例模式”
    在一定数量范围内可以创建多个,或者不同的类可以有多个、相同的类只能有一个

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

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

相关文章

空调原理与结构、制冷剂类型及相关先进技术

一、空调相关知识 1. 空调定义 空调是指利用各种技术和设备对某一空间内空气的温度、湿度、洁净度和流速进行调节,以满足人们对舒适性要求或不同工艺环境要求。 2. 基本原理 蒸发吸热、冷凝放热 压力越低沸点越低 3. 空调主要结构 空调主要由压缩机、冷凝器、…

求臻医学:MRD的十八般武艺 AI双驱动流派

作为专注于肿瘤精准诊疗领域的国家高科技企业,求臻医学依托《中国肿瘤基因图谱计划》和《肿瘤精准医学大数据平台》项目,围绕肿瘤诊断监测、预后评估、肿瘤早筛、遗传筛查、药物研发服务等场景,开发了针对肺癌、结直肠癌、胃癌、前列腺癌等实…

【Qt高阶】Qt D-Bus 简介【2023.10.16】

Qt D-Bus介绍 简介总线技术名词消息(阐述总线的消息内涵)服务名对象路径接口备忘表(便于记住名字的格式)调试 麒麟V10 与D-Bus 简介 D-Bus 是一个进程间通信(IPC)和远程过程调用(RPC)机制,最初是为了 Linux 开发,用来取代现有的竞争的 IPC 解决方案,提供…

交通部 EDI是什么?如何处理?

交通部于1996年开始实施《国际集装箱运输电子信息传输和运作系统及示范工程》,即在中国远洋运输集团、上海口岸、宁波口岸、天津口岸和青岛口岸建立 EDI 示范工程。 交通部 EDI 的数据结构 电子口岸或者其他物流企业需要确保能够生成和解析符合交通部要求的EDI数据…

两个pdf合并成一个pdf?

两个pdf合并成一个pdf?pdf合并是我们在处理PDF中非常常见的一个操作。我们看似有很多方法能够实现这一操作,但是真正适合自己的方法确实能够帮助我们很多。那么多方法的话,小编今天打算汇总几个比较适合新手的快速方法,这样效率更…

建立线上线下一体化营销体系,数字化营销系统必不可少

​在当今的市场环境中,实体行业想要取得持续的收入增长,必须将线上线下业务相结合,充分利用数字化营销系统的功能,以构建“全链路式”数字化营销体系。 而数字化营销系统中,常见的如分销系统、拼团系统、分红系统、积分…

mission planner通过串口连接3DR数传,远程飞控

前提 pixhaw2.4.8已布线,有单独的电源供电,通过电量计接power接口 电量计的输入端接24V电源,飞控的输入是5v电源,电量计上有个模块可以分压将5v的电输入到飞控 数传接在接口telem 2上(一个接飞控,一个接电…

用浏览器进行web应用测试,你会怎么做?

有没有遇到这样的一个场景:你在使用浏览器进行web应用测试,但是你想知道你在测试过程中的前端输出和后端响应的情况究竟如何。那么,你会怎么做呢?想必大多人会毫不犹豫地回答:通过浏览器console面板和network面板抓取信…

idea使用debug无法启动,使用run可以启动

1、将调试断点清除 使用快捷键ctrl shift F8,将勾选的选项去除即可 2、Error running SampleApplication: Command line is too long. Shorten command line for SampleApplication or also for Spring Boot default configuration,报这种错误&#x…

信号完整性分析基础知识之有损传输线、上升时间衰减和材料特性(五):有损传输线的特性阻抗和信号传输速度

有损传输线的特性阻抗 理想有损传输线特性阻抗是和频率相关的,很复杂。可以有以下公式: 按照代数知识,特性阻抗的实部和虚部如下: 其中RL表示单位长度导体的串联电阻 CL表示单位长度电容 LL单位长度串联环路电感 GL电介质单位长度…

番茄小说推文怎么申请授权?

以下为申请步骤 1.使用“巨量推文” 2.找到番茄小说这个小说app 3.按照要求申请关键词 完成以上步骤即可申请番茄小说推文关键词授权

前端新特性:Compute Pressure API!!!

前几天,review 同事代码的时候发现了一个新的 JS API PressureObserver。 通过一番搜索,发现这个 API 是 Compute Pressure API 的一部分。 Compute Pressure API:https://www.w3.org/TR/compute-pressure/ 它的作用是可以观察 CPU 的变…

yolov8如何进行训练验证推理

1、新建脚本main.py,也可以建一个yaml文件(避免改到default.yaml),这个yaml文件是在训练时用到 batchsize什么的都可以在yaml文件改,这俩东西不用填 2、两种训练的方法,用的时候可以注释掉其他 from u…

【无标题】三分钟快速实现MQTT网关远程连接三菱系列PLC

MQTT协议网关串口连接三菱FX3UPLC操作说明v1.2 目录 一. 使用流程 二. 准备工作 2.1 需要准备如下物品 2.2 LF220网关准备工作 2.3 PLC准备工作 2.4 电脑的准备工作 2.5 MQTT服务器准备工作 三. 腾讯云平台配置步骤 3.1 创建产品 3.2 添加设备 3.3 获取…

Python 中的变量Variable

六、Python 中的变量 1、变量的创建和赋值 在 Python 程序中,变量是用一个变量名表示,可以是任意数据类型,变量名必须是大小写英文、数字和下划线(_)的组合,且不能用数字开头,比如: a=88这里的 a 就是一个变量,代表一个整数,注意一点是 Python 是不用声明数据类型…

h5端自动滑动轮播效果实现

一、客户需要的效果图 二、具体代码实现如下&#xff1a; dom:<div class"swiper-container"> <div class"swiper-wrapper ul" click"setInputText"> <div class"swiper-slide li" v-for"(item, index) in answe…

如何使用内网穿透实现U8用友ERP本地部署并远程访问办公?

文章目录 前言1. 服务器本机安装U8并调试设置2. 用友U8借助cpolar实现企业远程办公2.1 在被控端电脑上&#xff0c;点击开始菜单栏&#xff0c;打开设置——系统2.2 找到远程桌面2.3 启用远程桌面 3. 安装cpolar内网穿透3.1 注册cpolar账号3.2 下载cpolar客户端 4. 获取远程桌面…

HarmonyOS/OpenHarmony原生应用开发-华为Serverless服务支持情况(四)

文档中的TS作者认为就是ArkTS之意。 一、云存储 AppGallery Connect&#xff08;简称AGC&#xff09;云存储是一种可伸缩、免维护的云端存储服务&#xff0c;可用于存储图片、音频、视频或其他由用户生成的内容。借助云存储服务&#xff0c;您可以无需关心存储服务器的开发、…

尾菜变宝、苹果富硒:青年学子秀农研成果 拼多多以赛促研助乡村振兴

青年兴则国兴&#xff0c;青年强则国强。一批新时代中国青年&#xff0c;仍保持着“自找苦吃”的精气神&#xff0c;冬天凿冰、夏天抗暑&#xff0c;以科技小院为依托&#xff0c;在服务三农建设中“解民生、治学问”。 10月14日&#xff0c;“中国研究生乡村振兴科技强农创新…

如何把视频压缩变小?

如何把视频压缩变小&#xff1f;大家都知道&#xff0c;视频一般都伴随着很大的文件体积&#xff0c;&#xff1a;尤其是现在的视频大多是高清晰度的&#xff0c;因此视频文件的体积就更加的大&#xff0c;视频体积太大会给我们带来很多的不便&#xff0c;主要是以下这几点&…