【创建型设计模式-单例模式】一文搞懂单例模式的使用场景及代码实现的7种方式

news2024/10/7 8:23:02

1.什么是单例模式

 在了解单例模式前,我们先来看一下它的定义:

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

 可以看到在定义中,提到了3个要素:

  1. 某一个类(单例类)只能有一实例;
  2. 单例类必须自行创建这个实例;
  3. 单例类必须向整个系统提供这个唯一的实例。

 OK,有了这三点,其实就把创建单例模式的步骤罗列出来了,我们先看一下单例模式的类图,有个模糊的概念。
在这里插入图片描述
 类图还是比较简单清晰的,自关联关系,下面我们来看下为什么要用单例模式呢?

2. 为什么用单例模式

 单例模式其实是很简单的一种模式,代码也很好实现,但是我在学习单例模式的时候,一直对它怎么使用比较模糊,这里涉及到两个疑问点,一是为什么要用单例模式,二是需在哪些场景下用单例模式呢?
 要搞清楚这两个问题,我们先从单例模式最常用的一个场景说起,就是线程池工具类,相信很多人都用过。我们看一下线程池的使用场景,在之前不使用线程池的时候,程序每次要执行一个现成任务,就会new一个新的线程,然后执行任务,再销毁线程。这个过程中,线程的创建和销毁对系统资源的开销是巨大的,如果使用线程池呢,在这个池中,始终维护一部分存活的线程,循环执行我们的任务,达到减少资源开销的目的。

ThreadPoolExecutor INSTANCE = new ThreadPoolExecutor(int corePoolSize,
                                int maximumPoolSize,
                                long keepAliveTime,
                                TimeUnit unit,
                                BlockingQueue<Runnable> workQueue,
                                ThreadFactory threadFactory,
                                RejectedExecutionHandler handler);

 在工具类中,我们会通过 ThreadPoolExecutor这个执行器创建线程池,在一个系统中,应该存在1个或者少量的线程池,多个任务复用池中的线程就可以。假设就以1个例,要复用这1个池中的多个线程,线程池必须有1份,否则每次调用工具类,都会new ThreadPoolExecutor创建1个线程池,也会伴随多个线程的创建和销毁动作,在用完里面的线程后就再也不用了,那线程池就没存在的意义了,既占用了系统的内存资源,又不符合业务场景,所以这里使用单例模式来维护唯一的线程池就很有必要了。
 看到这里,为什么要使用单例模式就比较清晰了,单个对象复用可以减少系统资源消耗,对于一些需要频繁创建和销毁的对象,使用单例模式无疑可以提高系统的整体性能。
 站在应用场景来看,一个类能不能做成单例,最容易区分的地方就在于,如果存在两个或两以上的实例会造成错误或业务场景上的歧义,也就是这个类在整个应用中,某一个时刻应该只有一个状态体现。那么除了刚才线程池工具类的例子,还有那些实际的应用场景呢?比如:操作系统中的“任务管理器”,“回收站”,网站的“计时器”,或者自定义数据库表的“自增主键计数器”等等,而且spring中的bean默认也是单例的。

3.单例模式的7种代码实现

 单例模式的代码实现有很多种,相信大家也都听过懒汉式与饿汉式,以及饿汉式下面的线程安全问题,其实真正开发中只要熟悉一到两种就可以,但是面试时经常被问到各种写法,接着就来看下这几种代码实现的写法吧。

3.1 饿汉式

  • 优点:类初始化时就创建此唯一的实例,不存在并发问题,能保证实例的唯一性。
  • 缺点:没有起到延迟加载的效果,如果此实例在整个应用声明周期中都不使用,会造成系统资源浪费。
  • 代码:
public class Singleton {

    // 1.构造方法私有化
    private Singleton() {}

    // 2.自行创建这个实例
    private static Singleton instance = new Singleton();

    // 3.提供外部可以访问的此实例的方法
    public static Singleton getInstance() {
        return instance;
    }
}

3.2 懒汉式-原始版本

  • 优点:效率高,延迟加载
  • 缺点:存在线程安全问题,并发场景下可能会创建多份实例,只能在单线程场景下使用
  • 代码:
public class Singleton {

    // 1.构造方法私有化
    private Singleton() { }

    // 2.自行创建这个实例
    private static Singleton instance = null;

    // 3.自行提供外部访问此实例的方法
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

 在多线程场景下,如果有两个现成同时执行到了 instance == null 且都成立,会重复创建实例。

3.3 懒汉式-线程安全版本

  • 优点:可以保证线程安全和实例的唯一性。
  • 缺点:锁住了整个获取实例的方法,效率较低。
  • 代码:
public class Singleton {

    // 1.构造方法私有化
    private Singleton() { }

    // 2.自行创建这个实例
    private static Singleton instance = null;

    // 3.自行提供全局访问此实例的方法
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

3.4 懒汉式-效率提升版本

  • 优点:锁范围变小,延迟加载
  • 缺点:仍然存在现成不安全的问题
  • 代码:
public class Singleton {

    // 1.构造方法私有化
    private Singleton() { }

    // 2.自行创建这个实例,注意用volatile修饰
    private static Singleton instance = null;

    // 3.自行给全局提供访问此实例的方法
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }
}

 这里虽然锁住了实例创建的代码片段,但是如果存在多个线程都进入到if (instance == null) {}判断里面,且等待锁释放的状态,也会造成创建多个实例的问题,是线程不安全的。

3.5 懒汉式 - 双重判断版

  • 优点:延迟加载,线程安全,锁定范围小
  • 缺点:代码略显复杂,可读性略低,其实可以忽略
  • 代码:
public class Singleton {

    // 1.构造方法私有化
    private Singleton() { }

    // 2.自行创建这个实例
    private static volatile Singleton instance = null;

    // 3.自行给全局提供访问此实例的方法
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

 由于存在双重判断,即便后来等待的线程持有锁之后,也会再次判断此实例是否已创建,但是要需要注意用volatile修饰,保证实例在线程之间的可见性。

3.6 静态内部类方式

  • 优点:线程安全,利用静态内部类的初始化创建实例,实现延迟加载,效率高。
  • 代码:
public class Singleton {

    // 1.构造方法私有化
    private Singleton() { }

    // 2.自行创建这个唯一的实力
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 3.自行给全局提供访问这个实例的方法
    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

3.7 枚举实现

  • 代码:
public enum SingletonEnum {
    
    INSTANCE;

    public String method() {
        return "what you need!";
    }
}

 枚举类的实现方式可以说是单例模式的最佳实践,在《Effective Java》这本书中,作者就提到“单元素的枚举类型已经成为实现Singleton的最佳方法”。
 枚举类的实现方式不仅可以解决上面所述的所有问题,还可以防止通过反射和反序列化来重复创建新的实例,Java虚拟机天然可以保证枚举对象的唯一性,在很多优秀的框架中,经常可以看到通过枚举实现的单例模式。

4.总结

 单例模式作为一种目标明确、结构简单、理解容易的设计模式,在开发工作中使用的频率相当的高,写在最后,简单总结一下单例模式的优缺点。

4.1 优点

  1. 单例模式提供了唯一实例的访问权限,可以限制客户端如何它;
  2. 对象的唯一性可以减少频繁创建和销毁对象过程,能够节省系统资源;
  3. 基于单例模式,可以扩展出指定个数的多例对象,即多例类,灵活性也很高。

4.2 缺点

  1. 单例模式没有抽象层,只有实现层,因此扩展困难;
  2. 在一定程度上为了单一职责,因为单例模式既提供了对象的创建职责,又提供了业务方法,导致创建过程和业务功能耦合在一块。
  3. 部分垃圾回收机制会回收长时间不用的对象,这将导致单例对象有被销毁的风险,下次使用重新被实例化,违背了单例模式的初衷(此条不太理解,有待验证,摘抄自《设计模式艺术》)。

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

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

相关文章

微型计算机原理速通期末复习

文章目录微机基础原码、反码、补码、移码溢出实数型功能结构8086/8088内部结构80286内部结构80386/80486内部结构标志寄存器FLAGS寄存器阵列段寄存器寻址标志寄存器EFLAGS分段结构数据寻址方式立即寻址直接寻址寄存器寻址寄存器间接寻址寄存器相对寻址基址-变址寻址基址-变址-相…

Solidity vs. Vyper:不同的智能合约语言的优缺点

本文探讨以下问题&#xff1a;哪种智能合约语言更有优势&#xff0c;Solidity 还是 Vyper&#xff1f;最近&#xff0c;关于哪种是“最好的”智能合约语言存在很多争论&#xff0c;当然了&#xff0c;每一种语言都有它的支持者。 这篇文章是为了回答这场辩论最根本的问题&…

磨金石教育摄影技能干货分享|中国风摄影大师——郎静山

说到中国风摄影&#xff0c;你想到的画面是什么样子的&#xff1f;故宫、长城、苏州园林、大红灯笼高高挂&#xff0c;反正离不开传承了千八百年的古建筑。仿佛没有了这些历史古董的元素就没有中国味道似的。 其实中国风&#xff0c;其内核应该是传统的审美观念和哲学思想。中…

【雷丰阳-谷粒商城 】课程概述

持续学习&持续更新中… 学习态度&#xff1a;守破离 【雷丰阳-谷粒商城 】课程概述该电商项目与其它项目的区别项目简介项目背景电商模式谷粒商城项目技术&特色项目前置要求谷粒商城-微服务架构图谷粒商城-微服务划分图参考该电商项目与其它项目的区别 互联网大型项目…

深入linux内核架构--内存管理

【推荐阅读】 代码大佬的【Linux内核开发笔记】分享&#xff0c;前人栽树后人乘凉&#xff01; 一文了解Linux内核的Oops 一篇长文叙述Linux内核虚拟地址空间的基本概括 路由选择协议——RIP协议 深入理解Intel CPU体系结构【值得收藏&#xff01;】 内存体系结构 1. UM…

银行测试人员谈测试需求

今天呢&#xff0c;想用故事说话&#xff0c;先看看啥叫用户需求挖掘。其实看完故事之后&#xff0c;我自己颇为震撼&#xff0c;请看。 故事一&#xff1a; 100多年前&#xff0c;福特公司的创始人亨利福特先生到处跑去问客户&#xff1a;“您需要一个什么样的更好的交通工具…

loganalyzer 展示数据库中的日志

1 实验目标&#xff1a; 利用rsyslog日志服务&#xff0c;将收集的日志记录于MySQL中&#xff0c;通过loganalyzer 展示数据库中的日志 2 环境准备 三台主机&#xff1a; 一台日志服务器&#xff0c;利用上一个案例实现&#xff0c;IP&#xff1a;192.168.100.100一台数据库…

【Java八股文总结】之数据结构

文章目录数据结构一、概念1、时间复杂度与空间复杂度2、常见算法时间复杂度3、Comparable二、常见的排序算法1、直接插入排序2、希尔排序3、选择排序4、堆排序5、冒泡排序6、快速排序7、归并排序8、二分查找算法Q&#xff1a;什么时候需要结束呢&#xff1f;三、线性表1、概念2…

使用 Footprint Analytics, 快速搭建区块链数据应用

Nov 2022, danielfootprint.network 如果你有一个处理 NFTs 或区块链的网站或应用程序&#xff0c;你可以在你的平台上直接向用户展示数据&#xff0c;以保持他们在网站或者应用内的参与&#xff0c;而不是链接以及跳出到其他网站。 对于任何区块链应用或者媒体、信息网站来说…

秦皇岛科学选育新品种 国稻种芯·中国水稻节:河北秸秆变肥料

秦皇岛科学选育新品种 国稻种芯中国水稻节&#xff1a;河北秸秆变肥料 秦皇岛新闻网 记者李妍 冀时客户端报道&#xff08;河北台 张志刚 米弘钊 赵永鑫 通讯员 赵力楠&#xff09; 新闻中国采编网 中国新闻采编网 谋定研究中国智库网 中国农民丰收节国际贸易促进会 国稻种芯…

无线通信技术概览

电生磁&#xff0c;磁生电 电场和磁场的关系&#xff0c;简而言之就是&#xff1a;变化的电场产生磁场&#xff0c;变化的磁场产生电场。 电荷的定向移动产生电流&#xff0c;电荷本身产生电场。电流是移动的电场。静止的电荷产生静止的电场&#xff0c;运动的电荷产生运动的电…

java实现阿里云rocketMQ消息的发送与消费(http协议sdk)

目录一、准备工作二、代码实现1.添加依赖2.创建一个常量类存放公共参数3.调用HTTP协议的SDK 发送普通消息4.调用HTTP协议的SDK 订阅普通消息三、配置main的日志输出级别四、测试效果五、完成代码一、准备工作 登录阿里云官网&#xff0c;先申请rocketMQ&#xff0c;再申请Topi…

一文带你了解PCB设计中的常用基本概念

本文将从初学者的角度出发&#xff0c;一文带你快速了解PCB设计中的常用基本概念&#xff1a;一、FR4板材FR-4就是玻璃纤维环氧树脂覆铜板&#xff0c;线路板中的一种基材&#xff0c;可以分为一般FR4板材和高TG FR4板材&#xff0c;Tg是玻璃转化温度&#xff0c;即熔点。电路板…

分享好玩的h5小游戏制作_为什么要做h5微信小游戏呢

近年来&#xff0c;市面上一直流行各种h5游戏&#xff0c;例如投票、答题、刮刮乐、大转盘等等等等&#xff0c;而且我在各种营销场景下经常看到它们的身影&#xff0c;是做促销&#xff0c;引流和宣传的神器之一&#xff01; 那么&#xff0c;怎么做好玩的h5游戏&#xff1f;还…

DIXml v5.21.0 for Delphi 11

DIXml v5.21.0 for Delphi 11 DIXml是一个嵌入式XML、XSLT&#xff0c;也是Delphi的EXSLT处理库(Embarcadero//CodeGear/Borland)。它构建在libxml2、libxslt和libexslt库上&#xff0c;但不需要更多的DLL或其他外部文件。 DIXml很容易成为Delphi中功能最齐全的XML和XSLT替代品…

实战讲解MyBatis缓存:一级缓存和二级缓存(图+文+源码)

1 缘起 回顾SpringBoot如何进行事务管理相关知识的时&#xff0c; 发现使用Spring的注解Transational即可实现事务管理&#xff0c;完成回滚操作&#xff0c; 然而SpringBoot中使用MyBatis这个ORM框架操作数据库&#xff0c;实现CURD&#xff0c; 这两者有什么关系呢&#xff…

集合类不安全

ArryList集合 多线程下不安全&#xff1b;可能会报错&#xff1a;java.util.ConcurrentModificationException&#xff08;并发修改异常&#xff09; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList;//java.util.ConcurrentModificationException 并发…

每日刷题3——牛客,算术转换和二级指针

更新不易&#xff0c;麻烦多多点赞&#xff0c;欢迎你的提问&#xff0c;感谢你的转发&#xff0c; 最后的最后&#xff0c;关注我&#xff0c;关注我&#xff0c;关注我&#xff0c;你会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我…

FFmpeg入门 - 视频播放

音视频最好从能够直接看到东西,也更加贴近用户的播放开始学起. 音视频编解码基础 我们可以通过http、rtmp或者本地的视频文件去播放视频。这里的"视频"实际上指的是mp4、avi这种既有音频也有视频的文件格式。 这样的视频文件可能会有多条轨道例如视频轨道、音频轨道…

(200,10)和(10,)的ndarray数组怎么计算内积,得到的是什么维度?

今天在定义一个内积运算的时候&#xff0c;发现了一个模糊的问题&#xff0c;两个变量&#xff0c;都是ndarray数组&#xff0c;一个维度是(200,10)&#xff0c;一个维度是(10,)&#xff0c;两者求内积后得到的新数组的维度是(200,)&#xff0c;该如何理解&#xff1f; 一、数…