01、创建型-单例模式--只有一个实例

在这里插入图片描述

文章目录

  • 前言
  • 一、基本介绍
    • 1.1 什么是单例模式
    • 1.2 为什么要用单例模式
    • 1.3 应用场景
    • 1.4 单例优缺点
  • 二、单例模式的实现方式
    • 2.1 饿汉式单例
      • 2.1.1 静态变量方式
      • 2.1.2 静态代码块
    • 2.2 懒汉式单例
      • 2.2.1 懒汉式单例
      • 2.2.2 懒汉式优化①-线程安全
      • 2.2.2 懒汉式优化②-双重检查锁
      • 2.2.3 懒汉式优化③-静态内部类
  • 三、小结

前言

  单例模式是设计模式中最简单但又最常用的的设计模式之一,是很多人学的第一个设计模式。引用百度百科的定义:单例模式创建的类在当前进程中,保证一个类只会被实例化一次,并提供了全局访问点,使用的时候通过单例提供的方法来获取实例。在确保线程安全的前提下,很多时候我们只需要同一个类的一个实例即可,而不是在任何使用的地方都实例化一个新对象,因此只能生成一个实例的模式就是单例模式。

一、基本介绍

1.1 什么是单例模式

  单例模式(Singleton Pattern) 是 Java 设计模式中最简单的设计模式之一,是指在内存中 只会且仅 创建一次对象的设计模式,无论什么时候都要保证这一点。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
  单例模式是指一个类只有一个实例,且该类能自行创建这个实例的一种模式,使用的时候通过单例提供的一个静态方法来获取实例。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。单例模式在现实生活中的应用也非常广泛,例如公司 CEO、部门经理等都属于单例模型,单例模式就是为了实现全局一个实例的需求,如下图所示。
在这里插入图片描述

注意:

  • 单例类保证内存里只有一个实例,减少了内存的开销。
  • 单例类必须自己创建自己的唯一实例
  • 单例类必须给所有其他对象提供这一实例

1.2 为什么要用单例模式

  在程序中多次使用同一个对象且作用相同时,对象需要频繁的创建和销毁的时候,而创建和销毁的过程由jvm执行,我们无法对其进行优化,为了防止内存飙升、减少了内存开支,单例模式可以让程序仅在内存中创建一个对象,并提供一个访问它的全局访问点,让所有需要调用的地方都共享这一单例对象。单例模式包含的角色只有一个,就是单例类——Singleton。

Heap
singleton
class1
class2
class3
.....
method1
method2
method3

  单例模式可以避免对资源的多重占用,避免出现多线程的复杂问题。单例类拥有一个私有构造函数,确保用户无法通过new关键字直接实例化它。除此之外,该模式中包含一个静态私有成员变量与静态公有的工厂方法,该工厂方法负责检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。

1.3 应用场景

  • 在应用场景中,某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
  • 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
  • 当某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
  • 应用程序的日志应用,一般都是单例模式实现,只有一个实例去操作才好,否则内容不好追加显示。
  • 多线程的线程池的设计一般也是采用单例模式,因为线程池要方便对池中的线程进行控制。

1.4 单例优缺点

  • 优点:
    1. 在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就防止其它对象对自己的实例化,确保所有的对象都访问一个实例。
    2. 单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
    3. 由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能,避免对共享资源的多重占用。
  • 缺点:
    1. 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
    2. 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
    3. 单例类的职责过重,在一定程度上违背了"单一职责原则"。
    4. 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。

二、单例模式的实现方式

  通常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。单例模式分为如下两类:

分类说明
饿汉式类加载的时候就会创建实例对象。
懒汉式类加载不会创建实例对象,而是首次使用该对象时才会创建。

2.1 饿汉式单例

2.1.1 静态变量方式

  饿汉式单例模式在类初实话的时候就会进行实例化,简单理解类似于一个"饥饿"的人,在程序启动时就要马上吃饭(创建实例),不管后续是否会真正需要使用这个实例。
  该模式的特点是通过静态修饰符修饰,类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了,在程序调用时直接返回该单例对象即可。这种方式比较常用,调用效率高,在类加载时就创建实例对象,因此不存在 线程安全 的问题。但不管程序用不用,实例都早以创建好,这对内存来说是种浪费,容易产生垃圾对象。

程序启动
类加载
创建单例对象
适用单例对象
返回单例对象
public class HungrySingleton {
    // 创建私有变量 ourInstance,用以记录 Singleton 的唯一实例。
    private static HungrySingleton HungryInstance = new HungrySingleton();
    
    // 把类的构造方法私有化,不让外部调用构造方法实例化
    private HungrySingleton() {}

    // 定义公有方法提供该类的全局唯一访问点,外部通过调用getInstance()方法来返回唯一的实例。
    public static HungrySingleton getInstance() {
        return HungryInstance;
    }
}

  饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。"饿汉式"比起“懒汉式”最大的优点就是没有加锁,执行效率会提高很多,但由于是在类加载时就初始化,容易产生垃圾对象,可能会导致资源浪费,尤其是在实例对象较大或者初始化过程较为复杂的情况下。

2.1.2 静态代码块

这种方式和上面没有基本没有什么区别,都会浪费内存。

public class HungrySingleton {
    private static HungrySingleton instance;

    private HungrySingleton() {
    }

    static {
        instance = new HungrySingleton();
    }

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

2.2 懒汉式单例

2.2.1 懒汉式单例

  懒汉式创建对象的方法是在真正使用对象前,先判断该对象是否已经实例化(判空),若已实例化直接返回该类对象,否则先执行实例化操作。只有在需要时才创建实例对象,节省了系统资源,具备懒加载功能。如下图所示:

YES
NO
适用单例对象
是否实例化
返回单例对象
实例化对象

  该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。我们创建一个 LazySingleton 类,LazySingleton 类将其构造函数作为私有,并具有自身的静态实例,使用 LazySingleton 类提供静态方法来获取 LazySingleton 对象。

public class LazySingleton {
    // 使用静态变量保存唯一实例
    private static LazySingleton instance;

    // 将构造方法设为私有,防止外部实例化
    private Singleton() {
    }

    // 提供全局访问点,获取唯一实例
    public static LazySingleton getInstance() {
        return instance == null ? new LazySingleton() : instance;
    }
}

  这种方式是最基本的实现方式,这种实现最大的好处就是第一次调用才初始化,如果没有用到该类,那么就不会实例化,从而节约资源,避免内存浪费。但缺点也比较明显,在多线程环境下是不安全的,如果多个线程能够同时进入,并且此时 instance 为 null,那么会有多个线程执行 instance == null,这将导致多次实例化 instance。

2.2.2 懒汉式优化①-线程安全

  不就是多线程嘛,加锁!由此,你得出了第一种优化方案,给 getInstance() 方法加锁:

public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {
        System.out.println("构造参数初始化");
    }
    
    // 保证每次只有一个线程进入getInstance()方法
    public static synchronized LazySingleton getInstance() {
        return instance == null ? new LazySingleton() : instance;
    }
}

  因为考虑到了多线程机制,实现起来比较麻烦,并且还会出现问题,就算是使用了一定的解救办法(同步、加锁、双重判断)的办法,保证在一个时间点只能有一个线程能够进入该方法,从而避免多次实例化 instance 的问题。但是当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,性能还是被损耗了,因此懒汉式方法的不推荐使用。

2.2.2 懒汉式优化②-双重检查锁

  很显然,加锁之后,达到了我们的目的,既实现了懒加载特效,也解决了线程安全问题。但是加锁之后,会导致该方法的执行效率特别低,其实就是初始化的时候才会出现线程安全,一旦初始化完成就不存在了。因此,需要把锁的粒度降低。不在方法上加锁,在关键问题上上锁。

public class Singleton {
    private Singleton() {
    }

    /**
     * 和饿汉模式相比,这边不需要先实例化出来,注意这里的 volatile,它是必须的
     */
    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            // 加锁
            synchronized (Singleton.class) {
                // 这一次判断也是必须的,不然会有并发问题
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

  通过双重检验锁机制来确保只有一个实例对象被创建,解决了懒汉式单例模式的性能、线程安全等问题,看起来是完美无缺的,其实是存在问题的,在多线程下,可能会出现空指针问题。

2.2.3 懒汉式优化③-静态内部类

  静态内部类单例模式由内部类创建,结合了懒汉式和饿汉式各自的优点,利用类加载机制保证了静态内部类只会被加载一次,并初始化其静态属性,从而保证了单例的线程安全性。而只有在需要时才会加载静态内部类,从而实现了延迟加载。

public class Singleton {
    // 私有的静态内部类
    private static class SingletonHolder {
        // 私有的静态变量
        private static final Singleton singletonFour = new Singleton();
    }
    
    private Singleton (){
        System.out.println("构造参数初始化");
    }

    public static final Singleton getInstance() {
        return SingletonHolder.singletonFour;
    }
}

  代码中增加了内部静态类 SingletonHolder,内部有一个singletonFour 的实例,并且也是类级别的。当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getInstance() 方法从而触发 SingletonHolder.singletonFour 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例。这种方式不仅具有延迟初始化的好处,而且由虚拟机提供了对线程安全的支持。看起来像是饿汉式,其实这是懒汉式。因为内部静态类是现在第一次使用的时候才会去初始化,所以SingletonHolder最初并未被初始化。

三、小结

  综上所述,单例模式作为一种简单但又非常重要的设计模式,在实际开发中有着广泛的应用,特别是在Spring框架等大型应用程序中。单例模式虽然简单,但是想写的严谨,还是需要考虑周全。
  当一个类的对象只需要或者只可能有一个时,应该考虑单例模式。如果一个类的实例应该在 JVM 初始化时被创建出来,应该考虑使用饿汉式。如果一个类的实例不需要预先被创建,也许这个类的实例并不一定能用得上,也许这个类的实例创建过程比较耗费时间,也许就是真的没必要提前创建,那么应该考虑懒汉式。非线程安全的懒汉式只能用于非并发的场景,局限性比较大,并不推荐使用。

把今天最好的表现当作明天最新的起点…….~

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ROS1快速入门学习笔记 - 04创建工作环境与功能包

一、定义 工作空间(workspace)是一个存放工程开发相关文件的文件夹。 src:代码空间(Source Space)build: 编辑空间(Build Space)devel:开发空间(Development Space)install:安装空间(Install …

深入理解Linux文件系统于日志分析

目录 一.Inode 和 block 概述 ​编辑 1.inode 的内容 (1)Inode 包含文件的元信息 (2)用 stat 命令可以查看某个文件的 inode 信息 (3) Linux系统文件三个主要的时间属性 (4)目…

CentOS 系统的优缺点

CentOS (社区企业操作系统的缩写)是一个基于红帽企业 Linux (RHEL)的免费开源发行版, 旨在为服务器和工作站提供稳定、可靠和安全的平台。 不应将其与CentOS Stream 混淆,后者是即将发布的 RHEL 版本的上游开发平台。 CentOS Li…

第67天:APP攻防-Frida反证书抓包移动安全系统资产提取评估扫描

思维导图 案例一:内在-资产提取-AppinfoScanne AppinfoScanner 一款适用于以 HW 行动/红队/渗透测试团队为场景的移动端(Android、iOS、WEB、H5、静态网站)信息收集扫描工具,可以帮助渗透测试工程师、攻击队成员、红队成员快速收集到移动端或者静态 WEB …

机器学习之sklearn基础教程

ChatGPT Scikit-learn (简称sklearn) 是一个非常受欢迎的Python机器学习库。它包含了从数据预处理到训练模型的各种工具。下面是一个关于如何使用sklearn进行机器学习的基础教程。 1. 安装和导入sklearn库 首先,你需要安装sklearn库(如果你还没有安装的…

使用写入这类接口后,文件指针fp是否会偏移?

以fprintf为例: 在使用 fprintf 函数写入数据时,文件指针 fp 会自动进行偏移,以确保数据被写入到文件的正确位置。 每次调用 fprintf 函数都会将数据写入文件,并且文件指针会在写入完成后自动移动到写入的末尾,以便下…

56-FMC连接器电路设计

视频链接 FMC连接器电路设计01_哔哩哔哩_bilibili FMC连接器电路设计 1、FMC简介 1.1、FMC介绍 FMC(FPGA Mezzanine Card)是一个应用范围、适应环境范围和市场领域范围都很广的通用模块。FMC连接器连接了由FPGA提供的引脚和FMC子板的I/O接口。最新的…

NLP方面知识

NLP方面知识 一 基础1.Tokenizer1.1 分词粒度:1.2 大模型的分词粒度1.3 各路语言模型中的tokenizer 2.Embedding layer2.1 理解Embedding矩阵 一 基础 1.Tokenizer tokenizer总体上做三件事情: 分词。tokenizer将字符串分为一些sub-word token string&…

ISSCC论文详解:“闪电”数模混合存内计算,适应transformer和CNNs架构

本文聚焦存内计算前沿论文ISSCC 2024 34.3,总结归纳其创新点,并对与之相似的创新点方案进行归纳拓展。 一、文章基本信息 ISSCC 2024 34.4:《A 22nm 64kb Lightning-Like Hybrid Computing-in-Memory Macro with a Compressed Adder Tree a…

实验七 智能手机互联网程序设计(微信程序方向)实验报告

请编写一个用户登录界面&#xff0c;提示输入账号和密码进行登录&#xff0c;要求在输入后登陆框显示为绿色&#xff1b; 二、实验步骤与结果&#xff08;给出对应的代码或运行结果截图&#xff09; index.wxml <view class"content"> <view class"a…

Linux——界面和用户

本篇文章所写的都是基于centos 7 64位&#xff08;通过虚拟机运行&#xff09;。 一、Linux的界面 Linux操作系统提供了多种用户界面&#xff0c;主要分为图形用户界面&#xff08;GUI&#xff09;和命令行界面&#xff08;CLI&#xff09;。 1、图形用户界面(GUI)&#xff…

2024 年选择安全运营中心 (SOC) 工具指南

安全运营中心 (SOC) 是对抗网络威胁的前线。他们使用各种安全控制措施来监控、检测和快速响应任何网络威胁。这些控制措施对于确保信息系统全天候安全至关重要。 大型组织中的现代 SOC 与各种安全供应商合作&#xff0c;处理 75 到 100 种不同的工具。让我们探讨一下您可能遇到…

vue【vuex状态管理】

1&#xff1a;vuex是什么&#xff1a; vuex是一个状态管理工具&#xff0c;状态就是指的数据&#xff0c;可以将数据存放到vuex中以供其他组件使用时进行调用 2&#xff1a;应用场景&#xff1a; ①&#xff1a;像用户登录客户端&#xff0c;这个用户的数据需要在多个组件中…

天锐绿盾 | 文件资料透明加解密系统

"天锐绿盾 | 文件资料透明加解密系统" 是一款专为企业及各类组织机构设计的数据安全防护软件。它以“透明加解密”为核心技术&#xff0c;旨在对用户的重要文件资料进行实时、无缝的加密保护&#xff0c;确保数据在存储、传输和使用过程中的安全性&#xff0c;防止敏…

javascript(第三篇)原型、原型链、继承问题,使用 es5、es6实现继承,一网打尽所有面试题

没错这是一道【去哪儿】的面试题目&#xff0c;手写一个 es5 的继承&#xff0c;我又没有回答上来&#xff0c;很惭愧&#xff0c;我就只知道 es5 中可以使用原型链实现继承&#xff0c;但是代码一行也写不出来。 关于 js 的继承&#xff0c;是在面试中除了【 this 指针、命名提…

一文速览Llama 3及其微调:如何通过paper-review数据集微调Llama3 8B

前言 4.19日凌晨正准备睡觉时&#xff0c;突然审稿项目组的文弱同学说&#xff1a;Meta发布Llama 3系列大语言模型了 一查&#xff0c;还真是 本文以大模型开发者的视角&#xff0c;基于Meta官方博客的介绍&#xff1a;Introducing Meta Llama 3: The most capable openly a…

基于FPGA轻松玩转AI

启动人工智能应用从来没有像现在这样容易&#xff01;受益于像Xilinx Zynq UltraScale MPSoC 这样的FPGA&#xff0c;AI现在也可以离线使用或在边缘部署、使用.可用于开发和部署用于实时推理的机器学习应用&#xff0c;因此将AI集成到应用中变得轻而易举。图像检测或分类、模式…

Android Studio查看viewtree

前言&#xff1a;之前开发过程一直看的是手机上开发者选项中的显示布局边界&#xff0c;开关状态需要手动来回切换&#xff0c;今天偶然在Android Studio中弄出了布局树觉得挺方便的。

国产FTP文件传输服务器需要具备哪些关键特性?

国产FTP文件传输服务器是指根据中国国内信息技术创新&#xff08;信创&#xff09;的要求和标准&#xff0c;自主研发的文件传输服务器软件。这类软件旨在替代传统的FTP服务器&#xff0c;以更好地适应国产化和信息安全的需要。国产FTP文件传输服务器通常需要具备以下要求&…

【嵌入式Linux】STM32P1开发环境搭建

要进行嵌入式Linux开发&#xff0c;需要在Windows、Linux和嵌入式Linux3个系统之间来回跑&#xff0c;需要使用多个软件工具。经过了4小时的安装&#xff08;包括下载时间&#xff09;&#xff0c;我怕以后会忘记&#xff0c;本着互利互助的原则&#xff0c;我打算把这些步骤详…
最新文章