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

news2024/12/28 5:49:54

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/32006.html

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

相关文章

北京东物流,南顺丰速运

配图来自Canva可画 众所周知&#xff0c;“双11”是一年一度的物流高峰期&#xff0c;但2022年“双11”当日快递业务量并未达到预期水平&#xff0c;全年增速创下新低。据了解&#xff0c;“双11”当日业务量为5.52亿件&#xff0c;同比下滑了20.69%&#xff0c;而11月1日至11…

什么是CISAW认证?有什么价值?

随着信息技术的快速发展和信息化应用的不断深入&#xff0c;信息技术、产品及网络已经融入社会经济生活的方方面面&#xff0c;但同时信息安全问题也越来越突出。面对严峻的信息安全形势&#xff0c;我国将信息安全上升至国家战略&#xff0c;相继出台了一系列政策法规。那大家…

IDEA好用插件推荐

一、MavenHelper 当Maven Helper 插件安装成功后&#xff0c;打开项目中的pom文件&#xff0c;下面就会多出一个试图Dependency Analyzer 切换到此试图即可进行相应操作&#xff1a; Conflicts&#xff08;查看冲突&#xff09;All Dependencies as List&#xff08;列表形式…

数据仓库规范

模型设计 模型设计概述 为什么需要模型设计&#xff1f; Linux 的创始人 Torvalds 有 一段关于“什么才是优秀程序员”的话:“烂程序员关心的是代码&#xff0c;好程序员关心的是数据结构和它们之间的关系”&#xff0c;其阐述了数据模型的重要性。有了适合业务和基础数据存…

python 中__init__ 作用

__init__的作用&#xff1a; &#xff08;1&#xff09;声明包 &#xff08;2&#xff09;预加载模块内容 &#xff08;1&#xff09;声明包 python项目结构中&#xff0c;普通目录下无__init__文件&#xff1b;而包下是有__init__文件的。 python 项目结构是按目录来组织的…

R语言结课及Matlab开始

R语言结课 我们R语言的学习这节课下课就结束了&#xff0c;接下来进行Matlab的学习。下面我会说一下R的结课任务及如何考试&#xff0c;以及我自己整理的Matlab安装教程。 R的结课作业&#xff1a;周二上课时提到的两个回归模型课程总结&#xff08;老师说作业总结主要是作业…

如何运用java代码操作Redis

目录 1、java如何连接Redis&#xff1f; 1.1.启动Redis服务 1.2.导入相关Redis依赖 1.3.java代码进行连接 2、java连接Redis 2.1.String 2.1.1.设值 2.1.2.拿值 2.1.3.删除 2.1.4.修改 2.1.5.给键值对设置过期时间 2.1.6.获取键值对剩余的存活时间 2.2.哈希&#xff08;Hash&a…

jacoco单测报告怎么同步到sonarqube

sonarqube支持多种代码覆盖率的报告展示&#xff0c;最常用的当属jacoco报告&#xff0c;那么jacoco的报告怎么同步到我们的sonarqube中呢&#xff1f; 我们先看看jacoco的offline模式&#xff08;单元测试&#xff09;报告生成的流程 根据上图我们需要生成单测报告&#xff0…

Apollo 应用与源码分析:CyberRT-工具与命令

概念 cyberRT包括一个可视化工具cyber_visualizer和两个命令行工具cyber_monitor和cyber_recorder。 注意&#xff1a;使用这些工具需要apollo docker环境 并且Cyber RT 中提供了一些命令工具&#xff0c;可以方便快捷的解决上述问题&#xff0c;本部分内容就主要介绍这些命…

Clion学习

看看Cmake是个什么&#xff1f; 他是个构建管理工具 一个比较OK的图 cmake_minimum_required(VERSION 3.15)#指定了最小的Cmake版本 project(jcdd)#指定了项目名称 set(CMAKE_CXX_STANDARD 14) add_executable(jcdd main.cpp)#输出可执行文件的名称安装第三方库&#xff…

图解来啦!机器学习工业部署最佳实践!10分钟上手机器学习部署与大规模扩展 ⛵

&#x1f4a1; 作者&#xff1a;韩信子ShowMeAI &#x1f4d8; 机器学习实战系列&#xff1a;https://www.showmeai.tech/tutorials/41 &#x1f4d8; 深度学习实战系列&#xff1a;https://www.showmeai.tech/tutorials/42 &#x1f4d8; 本文地址&#xff1a;https://www.sho…

【MyBatis】动态SQL

if标签 CarMapper.java /*** 多条件查询* param brand 品牌* param guidePrice 指导价* param carType 汽车类型* return*/List<Car> selectByMultiCondition(Param("brand") String brand,Param("guidePrice") Double guidePrice,Param("car…

MySQL基础篇之MySQL概述

01、MySQL概述 1.1、数据库相关概念 1、数据库相关概念 名称解释说明简称数据库存储数据的仓库&#xff0c;数据是有组织的进行存储DataBase&#xff08;DB&#xff09;数据库管理系统操纵和管理数据库的大型软件DataBase Management System&#xff08;DBMS&#xff09;SQL…

ky使用教程(基于fetch的小巧优雅js的http客服端)

1.前言 react项目更加倾向于使用原生的fetch请求方式&#xff0c;而ky正是底层使用fetch的api做请求。github星数是8.2K&#xff0c;源码地址是&#xff1a;GitHub - sindresorhus/ky: &#x1f333; Tiny & elegant JavaScript HTTP client based on the browser Fetch A…

树上背包dp

“我们终其一生不过是为了一个AC罢了” 软件安装 嗯…这个题又调了一个下午&#xff0c;不过俺的确对dp方程有了一些理解 这个题没啥难的&#xff0c;不过是这个转移方程不太好想&#xff0c;过于抽象了&#xff0c;之前一直不理解树上背包是啥&#xff0c;现在理解了&#xff…

Xftp 无法连接 Debian

Xftp 无法连接 Debian检查网络是否配置有问题检查是不是防火墙没有关闭首先检查主机防火墙检查Debian防火墙启动SSH服务检查网络是否配置有问题 发现主机和Debian在一个网段。说明配置没有问题。 检查是不是防火墙没有关闭 首先检查主机防火墙 检查Debian防火墙 显然都没有开…

并行多核体系结构基础知识

目录分类MIMD计算机分类并行编程并行编程模型共享存储并行模型针对LDS的并行编程存储层次结构缓存一致性和同步原语缓存一致性基础对同步的硬件支持存储一致性模型和缓存一致性解决方案存储一致性模型高级缓存一致性设计互连网络体系结构分布式操作系统SIMT体系结构分类 根据Fl…

MCE | 磁珠 VS 琼脂糖珠

琼脂糖珠 长久以来&#xff0c;多孔的琼脂糖珠 (也称琼脂糖树脂) 作为免疫沉淀实验中的固相支持物常用的材料。琼脂糖珠海绵状的结构 (直径 50-150 μm) 可以结合抗体 (继而结合靶蛋白)&#xff0c;它能够直接高效、快速结合抗体&#xff0c;而不需借助特殊的专业设备。 图 1.…

【.Net Core】上传文件-IFormFile

文章目录安全注意事项存储方案文件上传方案.NET Core Web APi FormData多文件上传&#xff0c;IFormFile强类型文件灵活绑定验证内容验证文件扩展名验证文件签名验证文件名安全大小验证使名称属性值与 POST 方法的参数名称匹配来源安全注意事项 向用户提供向服务器上传文件的功…

openfeign调用文件服务的文件上传接口报错:Current request is not a multipart request

今天在用Swagger测试项目中文件服务的文件上传接口时发现接口调用异常。 异常展示 笔者这里罗列下Swagger上的错误显示、文件服务的异常以及服务调用方的异常。 【Swagger的异常】 【服务调用方的控制台异常】 【文件服务的控制台异常】 代码展示 【服务调用方的Controller…