Java二十三种设计模式-单例模式(1/23)

news2024/9/22 3:55:08

引言

在软件开发中,设计模式是一套被反复使用的、大家公认的、经过分类编目的代码设计经验的总结。单例模式作为其中一种创建型模式,确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的概念、实现方式、使用场景以及潜在的问题。

基础知识,java设计模式总体来说设计模式分为三大类:

(1)创建型模式,共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

(2)结构型模式,共7种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

(3)行为型模式,共11种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

第一部分:单例模式概述

1.1 单例模式定义

单例模式是一种常用的软件设计模式,其核心思想是确保一个类在任何情况下都只有一个实例,并且提供一个全局访问点来获取这个唯一的实例。这种模式在需要全局状态信息或者需要频繁创建和销毁实例会导致资源浪费的情况下非常有用。

为什么需要单例模式?

  • 全局访问点:在某些情况下,我们需要一个全局的访问点来操作某些资源或状态,例如配置信息管理器或数据库连接池。
  • 资源优化:避免创建多个实例导致的资源浪费,例如线程池或缓存。
  • 控制状态:在多线程环境下,控制对共享资源的并发访问,确保数据的一致性。

1.2 单例模式的特点

懒汉式单例模式

懒汉式单例模式的核心特点是“按需实例化”,即只有在第一次调用getInstance()方法时才会创建实例。

特点:
  • 延迟加载:只有在真正需要使用实例时才进行加载,可以提高系统启动速度。
  • 线程不安全:在多线程环境下,如果没有适当的同步措施,可能会导致多个实例被创建。
示例代码(不同步):
public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {}

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

饿汉式单例模式

与懒汉式相对,饿汉式单例模式在类加载时就立即创建实例。

特点:
  • 线程安全:由于实例在类加载时就已经创建,因此不存在多线程同步问题。
  • 资源浪费:如果实例从未被使用,也会造成资源浪费。
示例代码:
public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton() {}

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

线程安全和线程不安全的单例模式

  • 线程安全的单例模式:通过同步机制确保在多线程环境下,getInstance()方法不会被多次调用,从而创建多个实例。
  • 线程不安全的单例模式:没有采取同步措施,可能会在多线程环境下创建多个实例。
线程安全的实现示例(双重检查锁定):
public class ThreadSafeSingleton {
    private static volatile ThreadSafeSingleton instance;

    private ThreadSafeSingleton() {}

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

在这部分内容中,我们介绍了单例模式的基本定义和两种主要的实现方式:懒汉式和饿汉式。接下来,我们将深入探讨单例模式的实现细节和使用场景。

第二部分:单例模式的实现

2.1 懒汉式单例模式

代码示例:

public class LazySingleton {

    // volatile关键字确保多线程环境下的内存可见性
    private static volatile LazySingleton instance;

    private LazySingleton() {
        // 防止通过反射攻击单例模式
        if (instance != null) {
            throw new IllegalStateException("Instance already exists!");
        }
    }

    public static LazySingleton getInstance() {
        // 双重检查锁定,减少不必要的同步
        if (instance == null) {
            synchronized (LazySingleton.class) {
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

优点和缺点:

  • 优点

    • 按需创建实例,节省资源。
    • 简单易懂,实现容易。
  • 缺点

    • 线程安全性需要额外处理,如示例中的双重检查锁定。
    • 反射和序列化可能破坏单例模式。

2.2 饿汉式单例模式

代码示例:

public class EagerSingleton {

    // 静态初始化,类装载时就完成实例化
    private static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton() {
        // 防止通过反射攻击单例模式
        if (instance != null) {
            throw new IllegalStateException("Instance already exists!");
        }
    }

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

优点和缺点:

  • 优点

    • 线程安全,无需额外同步。
    • 实现简单。
  • 缺点

    • 无论是否使用,类装载时就完成实例化,可能导致资源浪费。
    • 可能影响类装载时间。

2.3 线程安全的单例模式

双重检查锁定(Double-Checked Locking)

  • 代码示例

    public class DoubleCheckedLockingSingleton {
    
        private static volatile DoubleCheckedLockingSingleton instance;
    
        private DoubleCheckedLockingSingleton() {
            // 防止通过反射攻击单例模式
            if (instance != null) {
                throw new IllegalStateException("Instance already exists!");
            }
        }
    
        public static DoubleCheckedLockingSingleton getInstance() {
            if (instance == null) {
                synchronized (DoubleCheckedLockingSingleton.class) {
                    if (instance == null) {
                        instance = new DoubleCheckedLockingSingleton();
                    }
                }
            }
            return instance;
        }
    }

  • 解释: 双重检查锁定首先检查实例是否存在,如果不存在,则进入同步块再次检查。这样避免了每次调用getInstance()时都要进行同步,提高了性能。

静态内部类

  • 代码示例

    public class StaticInnerClassSingleton {
    
        private StaticInnerClassSingleton() {
            // 防止通过反射攻击单例模式
            if (InstanceHelper.INSTANCE != null) {
                throw new IllegalStateException("Instance already exists!");
            }
        }
    
        private static class InstanceHelper {
            private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
        }
    
        public static StaticInnerClassSingleton getInstance() {
            return InstanceHelper.INSTANCE;
        }
    }

  • 解释: 静态内部类利用了类装载的机制来保证初始化实例时的线程安全。静态内部类只有在第一次使用时才会装载,此时可以完成对单例对象的初始化。

2.4 枚举实现单例模式

代码示例:

public enum EnumSingleton {

    // 实例化枚举
    INSTANCE;

    // 可以添加其他方法
    public void doSomething() {
        // ...
    }
}

优点:

  • 自然实现单例: 枚举在Java语言中天然是单例的,每个枚举常量在JVM中只有一个实例。
  • 线程安全: 枚举实例的创建是线程安全的。
  • 防止反射和序列化攻击: 枚举类型本身可以防止反射和序列化攻击,因为枚举类型不允许通过反射创建枚举实例。

通过上述实现方式,我们可以看到单例模式的多样性和灵活性。每种实现方式都有其适用场景和优缺点,开发者需要根据实际情况选择最合适的实现方法。在下一部分中,我们将探讨单例模式的使用场景和潜在问题。

 

第三部分:单例模式的使用场景

单例模式因其确保唯一实例的特性,在软件系统中有多种应用场景。本节将讨论几个典型的使用案例。

3.1 数据库连接池

为什么数据库连接池适合使用单例模式?

数据库连接池管理着数据库连接的创建、使用和销毁过程,其目的是减少频繁创建和销毁连接的开销,提高资源利用率。

  • 资源复用:单例模式确保连接池全局只有一个实例,所有需要数据库连接的部分都使用同一个池,避免了创建多个连接池带来的资源浪费。
  • 统一管理:通过单例模式,可以集中管理数据库连接的生命周期,包括连接的创建、监控和销毁。
  • 性能优化:数据库连接的创建是一个耗时的操作,单例模式使得连接池可以在系统启动时初始化,并在运行期间复用这些连接,从而提高系统性能。

3.2 配置管理器

讨论配置信息管理中使用单例模式的优势。

配置管理器负责存储和提供应用的配置信息,如数据库配置、服务地址等。

  • 全局访问:单例模式提供了一个全局访问点,使得系统中所有需要配置信息的部分都能方便地获取到最新的配置数据。
  • 数据一致性:确保所有部分使用的是同一个配置管理器实例,从而保证了配置数据的一致性。
  • 简化配置更新:当配置信息发生变化时,只需更新单例配置管理器中的信息,所有依赖配置的地方都会获得更新后的数据。

3.3 硬件资源管理器

讨论硬件资源管理中使用单例模式的场景。

硬件资源管理器负责对打印机、扫描仪等硬件设备进行控制和访问管理。

  • 设备控制:硬件设备通常希望被控制系统以单例模式管理,以避免多个实例同时发送指令造成冲突。
  • 状态同步:单例模式可以确保所有对硬件状态的更改都是同步的,防止因多实例并发操作导致的状态不一致问题。
  • 减少资源消耗:硬件资源通常有限,单例模式可以减少对这些资源的重复申请和占用。

在这些场景中,单例模式的应用可以带来诸多好处,包括资源优化、数据一致性保证以及简化的系统设计。然而,单例模式的使用也需慎重考虑,以避免其潜在的问题,如测试困难、扩展性限制等。在后续部分,我们将进一步探讨单例模式的潜在问题和最佳实践。

第四部分:单例模式的潜在问题

4.1 测试困难

单例模式可能会给单元测试带来一些挑战,因为它依赖于全局状态。

  • 全局状态问题:单例模式提供了全局访问点,这可能导致测试之间的相互影响,使得测试结果不可预测。
  • 模拟困难:在需要模拟或替换单例组件的行为时,由于其全局性,很难进行模拟(Mocking)。
  • 解决方案:可以通过使用依赖注入、接口或者使用测试框架提供的特定技术来解决这些问题。

4.2 扩展性问题

随着系统的发展,单例模式可能会成为系统扩展的瓶颈。

  • 紧耦合问题:单例模式可能使得系统组件之间存在紧耦合,这在需要扩展或修改单例组件时可能导致问题。
  • 服务降级:在分布式系统中,单例模式可能不再适用,因为它无法很好地支持服务的横向扩展。

4.3 多线程环境问题

在多线程环境中使用单例模式时,需要特别注意线程安全问题。

  • 竞态条件:如果多个线程同时访问单例实例的创建过程,而这个过程中没有适当的同步机制,可能会导致创建多个实例。
  • 性能问题:过度的同步可能会降低系统性能,特别是在高并发场景下。

第五部分:单例模式与其他设计模式的比较

5.1 单例模式与工厂模式

单例模式和工厂模式都涉及到类的实例化,但它们的目的和使用场景不同。

  • 单例模式:确保一个类只有一个实例,并提供一个全局访问点。
  • 简单工厂模式:创建对象的接口,让子类决定实例化哪一个类。

5.2 单例模式与原型模式

原型模式提供了通过复制现有的实例来创建新实例的能力,与单例模式形成对比。

  • 单例模式:关注于限制实例的数量,确保全局只有一个实例。
  • 原型模式:关注于通过复制来快速创建新实例,适用于创建复杂对象。

第六部分:最佳实践和建议

6.1 何时使用单例模式

单例模式适用于管理共享资源,如配置信息、连接池等。

  • 全局状态管理:当全局状态需要被整个应用共享时。
  • 资源优化:当创建多个实例会导致资源浪费时。

6.2 避免滥用单例模式

滥用单例模式可能导致代码难以测试和维护。

  • 避免全局状态:尽量减少依赖全局状态,因为它可能导致代码难以理解和测试。
  • 替代方案:考虑使用依赖注入等技术来减少对单例模式的依赖。

6.3 替代方案

依赖注入是一种常用的替代单例模式的技术。

  • 依赖注入:通过依赖注入框架,可以将对象的创建和管理交给框架来处理,从而减少对单例模式的依赖。
  • 服务定位器模式:提供了一种查找服务的方式,可以作为单例模式的替代方案。

通过深入分析单例模式的潜在问题和最佳实践,我们可以更明智地决定何时以及如何使用单例模式。在实际开发中,我们应该根据具体需求和上下文来选择最合适的设计模式。

结语

单例模式是一种简单但强大的设计模式,正确使用可以带来诸多好处,但也需要谨慎处理以避免潜在问题。通过本文的深入分析,希望读者能够对单例模式有更全面的理解,并在实际开发中做出合理的设计选择。

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

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

相关文章

C语言 ——— 模拟实现strcpy函数

目录 strcpy函数功能介绍 strcpy函数的模拟实现 strcpy函数功能介绍 学习并使用strcpy函数-CSDN博客 strcpy函数的模拟实现 代码演示&#xff1a; #include<stdio.h> #include<assert.h> char* my_strcpy(char* destination, const char* source) {assert(des…

单目3D和bev综述

文章目录 SOTA2D 检测单目3d检测3d bev cam范式1 Transformer attention is all you need 20172 ViT vision transformer ICLR 2021google3 swin transformer 2021 ICCV bestpaper MS4 DETR 20205 DETR3D 20216 PETR 20227 bevformerLSSbevdetcaddn指标 mAP NDS标注&#xff1a…

kubernetes——Istio(三)

一、安全 将单一应用程序分解为微服务可提供各种好处&#xff0c;包括更好的灵活性、 可伸缩性以及服务复用的能力。但是&#xff0c;微服务也有特殊的安全需求&#xff1a; 为了抵御中间人攻击&#xff0c;需要流量加密。为了提供灵活的服务访问控制&#xff0c;需要双向 TL…

【P2P_BMA_P2MP_NBMA】

基本概念介绍 1. BMA&#xff08;Broadcast&#xff09; 广播型多路访问技术&#xff0c;在一个MA&#xff08;多路访问&#xff0c;在一个网段内的节点数量不限制。&#xff09;网络中同时存在广播机制。 特点&#xff1a; 允许将数据包广播到网络上的所有主机。路由器之间…

科普文:微服务技术栈梳理

概叙 如上两图所示&#xff0c;微服务架构下&#xff0c;需要的组件很多&#xff0c;上面中也并未列全。下面将梳理一下国内微服务架构下&#xff0c;用到的技术栈&#xff0c;仅供参考。 科普文&#xff1a;12种常见的软件架构-CSDN博客 没有最好的架构&#xff0c;只有最适…

开启音乐新纪元,AI人工智能创新歌词

在音乐的漫长历史长河中&#xff0c;每一次的创新都如同璀璨星辰&#xff0c;照亮了前行的道路。如今&#xff0c;人工智能的崛起正引领着音乐创作步入一个全新的纪元&#xff0c;为歌词领域带来了前所未有的变革。 “妙笔生词智能写歌词软件&#xff08;veve522&#xff09;”…

智慧园区智能化解决方案PPT(173页)

智慧园区智能化解决方案摘要 智慧园区智能化解决方案是一项综合性的系统工程&#xff0c;它通过集成先进的信息技术&#xff0c;实现园区管理的自动化、智能化&#xff0c;提高园区的安全性、效率和舒适度。本文详细介绍了某智慧园区项目的规划与设计&#xff0c;该项目建筑面…

python的字符串

字符串 简单操作 创建 利用 ‘ ’ 或 “ ” 将字符或数字包裹起来的都为字符串 a"你好" 格式化字符串 元组的字符格式化 字符串格式化函数 srt.format() f格式化 方法 split()//指定分割符经行分割 strip()//指定移除字符头尾的字符 join()//指定序列中的字符连接成新…

C#学习

C#学习 1.B站丑萌气质狗C#的循环-判断泛型错误处理面向对象static的使用定义showInfo类和Hero类 在这里插入图片描述 然后在该解决方案add新建一个类库&#xff0c;点击rebuild&#xff0c;会在bin文件夹下生成.dll文件 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direc…

SAC-IA粗配准算法记录

1. 算法思路 SAC-IA(Sample Consensus Initial Aligment,SAC-IA)粗配准算法是一种基于局部特征描述子的点云粗配准算法,其需要计算点云的快速点特征直方图(FPFH)来保持对应点对之间的相似关系,根据相似关系来搜索点云中的对应点。其基本原理是采用采样一致性的思想,通过查…

Zabbix6.0使用自带模板(Redis by Zabbix agent 2)监控Redis数据库

注意&#xff1a;Zabbix6.0使用Redis by Zabbix agent 2 模板可直接监控Redis数据。 1、添加Redis账号密码信息(如果Redis没有设置密码可省略此步骤) vim zabbix_agent2.confPlugins.Redis.Sessions.redis.Uritcp://redis.huayunworld.com:6379 Plugins.Redis.Sessions.redis…

工具推荐|语音轻松记笔记,AI帮你识别和润色

# 你日常有没有遇到这样的场景&#xff1f; 偶尔有一些奇思妙想想要记录下来&#xff0c;但没有一个轻量的工具&#xff0c;往往会想着想着就把这个想法抛之脑后。特别是搞短视频的&#xff0c;你也许希望把当时的想法录下来&#xff0c;稍微剪辑下就能出一条不错的口播视频。…

外泌体相关基因肝癌临床模型预测——2-3分纯生信文章复现——5.拷贝数变异及突变图谱(1)

内容如下: 1.外泌体和肝癌TCGA数据下载 2.数据格式整理 3.差异表达基因筛选 4.预后相关外泌体基因确定 5.拷贝数变异及突变图谱 6.外泌体基因功能注释 7.LASSO回归筛选外泌体预后模型 8.预后模型验证 9.预后模型鲁棒性分析 10.独立预后因素分析及与临床的相关性分析…

CMU 15-213 CSAPP. Ch9. Virtual Memory

CMU 15-213 CSAPP (Ch1~Ch3) CMU 15-213 CSAPP (Ch5~Ch7) CMU 15-213 CSAPP (Ch8) CMU 15-213 CSAPP (Ch9) CMU 15-213 CSAPP (Ch10) 视频链接 课件链接 课程补充 该课程使用 64位 编译器&#xff01; Ch9. Virtual Memory 9.1 Address spaces 将内存看成数组&#xff0c;物…

OpenGL笔记十二之实现三角形在屏幕横向上往复运动的动画

OpenGL笔记十二之实现三角形在屏幕横向上往复运动的动画 —— 2024-07-14 晚上 bilibili赵新政老师的教程看后笔记 code review! 文章目录 OpenGL笔记十二之实现三角形在屏幕横向上往复运动的动画1.运行2.vs3.fs4.main.cpp的关键部分 1.运行 2.vs #version 330 core layout …

成都工业学院2022级数据库原理及应用专周课程学生选课系统(进阶篇)

运行环境 操作系统&#xff1a;Windows 11 家庭版 运行软件&#xff1a;Visual Studio Code Navicat Premium 16 进阶内容 过程函数改为触发器 例如将学生选课的过程函数改为对选课表添加触发器 使用ruoyi-vue实现可视化 配置并运行ruoyi-vue 进行代码生成 将生成的代码添…

【Linux】03.权限

一、权限的概念 Linux下有两种用户&#xff1a;超级用户&#xff08;root&#xff09;、普通用户。 超级用户&#xff1a;可以在 linux 系统下做任何事情&#xff0c;不受限制普通用户&#xff1a;在linux下做有限的事情超级用户的命令提示符是“#”&#xff0c;普通用户的命…

ctfshow-web入门-php特性(web104-web108)

目录 1、web104 2、web105 3、web106 4、web107 5、web108 1、web104 需要传入的 v1 和 v2 进行 sha1 加密后相等。 解法1&#xff1a; 这里都没有判断 v1 和 v2 是否相等&#xff0c;我们直接传入同样的内容加密后肯定也一样。 ?v21 post&#xff1a; v11 拿到 flag…

C++从入门到起飞之——输入输出!

目录 1.命名空间 1.1namespace的价值 1.2namespace的定义 1.3命名空间使⽤ 2.C输⼊&输出 3.完结散花 个人主页&#xff1a;秋风起&#xff0c;再归来~ C从入门到起飞 个人格言&#xff1a;悟已往之不谏&#xff0c;知来者犹可追 克心守己…

Redis中的持久化详解

本篇文章会对Redis的持久化进行详解。主要涉及到的方面有&#xff1a;redis为什么需要持久化、redis怎么进行的持久化、持久化的方式都有哪些、每种持久化方式的优缺点是什么、持久化的流程进行展开详解。希望本篇文章会对你有所帮助。 文章目录 一、持久化简介 二、Redis的持久…