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

引言

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

基础知识,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 替代方案

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

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

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

结语

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

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

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

相关文章

捷配生产总结-PCB上器件布局不好对SMTDIP的影响

在电子制造领域,PCB(印刷电路板)的设计至关重要,其中器件的布局更是影响着整个生产流程的效率和质量。特别是对于 SMT(表面贴装技术)和 DIP(双列直插式封装)这两种常见的组装工艺&am…

Dify中Jieba类的create()方法实现过程

本文主要介绍Dify中Jieba类的create()方法执行过程,重点是段(segment)的关键词的生成。 一.create方法流程概述 整个create方法的目的是为了处理一批文本,提取它们的关键词,并更新关键词表,以便于后续的关…

Redis 框架 jedis 与 lettuce 比较

【需求背景】 由于集群模式下 Spring 对 jedis 的封装,在使用批量方法 (mget、delete) 时会把任务都提交到仅有一个核心线程的 executor 中执行,在高并发场景下会造成应用内大量任务处于排队状态而得不到执行。 具体参考:https://juejin.cn…

CTF之easyupload

拿到题目发现是文件上传的漏洞&#xff0c;但是这个黑名单过滤的有点严格&#xff0c;无论是文件里还是文件后缀都不能出现php 那我们就用<?eval($_POST[a]);?>来进行绕过&#xff08;注意这里要加个GIF89a或者GIP87a进行欺骗&#xff09; 但是后缀依然不能绕过怎么办&…

江协科技51单片机学习- p27 I2C AT24C02存储器

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

【数据结构】栈和队列的深度探索,从实现到应用详解

&#x1f48e;所属专栏&#xff1a;数据结构与算法学习 &#x1f48e; 欢迎大家互三&#xff1a;2的n次方_ &#x1f341;1. 栈的介绍 栈是一种后进先出的数据结构&#xff0c;栈中的元素只能从栈顶进行插入和删除操作&#xff0c;类似于叠盘子&#xff0c;最后放上去的盘子最…

【题解】—— LeetCode一周小结28

&#x1f31f;欢迎来到 我的博客 —— 探索技术的无限可能&#xff01; &#x1f31f;博客的简介&#xff08;文章目录&#xff09; 【题解】—— 每日一道题目栏 上接&#xff1a;【题解】—— LeetCode一周小结27 8.寻找数组的中心下标 题目链接&#xff1a;724. 寻找数组的…

【CICID】GitHub-Actions-SpringBoot项目部署

[TOC] 【CICID】GitHub-Actions-SpringBoot项目部署 0 流程图 1 创建SprinBoot项目 ​ IDEA创建本地项目&#xff0c;然后推送到 Github 1.1 项目结构 1.2 Dockerfile文件 根据自身项目&#xff0c;修改 CMD ["java","-jar","/app/target/Spri…

docker部署canal 并监听mysql

1.部署mysql 需要开启mysql的binlong&#xff0c;和创建好用户等 可以参考这个 Docker部署Mysql数据库详解-CSDN博客 2.部署canal 参考这一篇&#xff1a; docker安装Canal&#xff0c;开启MySQL binlog &#xff0c;连接Java&#xff0c;监控MySQL变化_docker canal-CSD…

简单搭建卷积神经网络实现手写数字10分类

搭建卷积神经网络实现手写数字10分类 1.思路流程 1.导入minest数据集 2.对数据进行预处理 3.构建卷积神经网络模型 4.训练模型&#xff0c;评估模型 5.用模型进行训练预测 一.导入minest数据集 MNIST--->raw--->test-->(0,1,2...) 10个文件夹 MNIST--->raw-…

【Linux】基于环形队列RingQueue的生产消费者模型

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 环形队列的概念及定义 POSIX信号量 RingQueue的实现方式 RingQueue.hpp的构建 Thread.hpp Main.cc主函数的编写 Task.hpp function包装器的使用 总结 前言…

《Python数据科学之一:初见数据科学与环境》

《Python数据科学之一&#xff1a;初见数据科学与环境》 欢迎来到“Python数据科学”系列的第一篇文章。在这个系列中&#xff0c;我们将通过Python的镜头&#xff0c;深入探索数据科学的丰富世界。首先&#xff0c;让我们设置和理解数据科学的基本概念以及在开始任何数据科学项…

《C专家编程》 C++

抽象 就是观察一群数据&#xff0c;忽略不重要的区别&#xff0c;只记录关注的事务特征的关键数据项。比如有一群学生&#xff0c;关键数据项就是学号&#xff0c;身份证号&#xff0c;姓名等。 class student {int stu_num;int id_num;char name[10]; } 访问控制 this关键字…

安全防御:防火墙概述

目录 一、信息安全 1.1 恶意程序一般会具备一下多个或全部特点 1.2 信息安全五要素&#xff1a; 二、了解防火墙 2.1 防火墙的核心任务 2.2 防火墙的分类 2.3 防火墙的发展历程 2.3.1 包过滤防火墙 2.3.2 应用代理防火墙 2.3.3 状态检测防火墙 补充防御设备 三、防…

Torch-Pruning 库入门级使用介绍

项目地址&#xff1a;https://github.com/VainF/Torch-Pruning Torch-Pruning 是一个专用于torch的模型剪枝库&#xff0c;其基于DepGraph 技术分析出模型layer中的依赖关系。DepGraph 与现有的修剪方法&#xff08;如 Magnitude Pruning 或 Taylor Pruning&#xff09;相结合…

uniapp实现水印相机

uniapp实现水印相机-livePusher 水印相机 背景 前两天拿到了一个需求&#xff0c;要求在内部的oaApp中增加一个卫生检查模块&#xff0c;这个模块中的核心诉求就是要求拍照的照片添加水印。对于这个需求&#xff0c;我首先想到的是直接去插件市场&#xff0c;下一个水印相机…

《Python数据科学之五:模型评估与调优深入解析》

《Python数据科学之五&#xff1a;模型评估与调优深入解析》 在数据科学项目中&#xff0c;精确的模型评估和细致的调优过程是确保模型质量、提高预测准确性的关键步骤。本文将详细探讨如何利用 Python 及其强大的库进行模型评估和调优&#xff0c;确保您的模型能够达到最佳性能…

docker中1个nginx容器搭配多个django项目中设置uwsgi.ini的django项目路径

docker中&#xff0c;1个nginx容器搭配多个django项目容器&#xff0c;设置各个uwsgi.ini的django项目路径 被这个卡了一下&#xff0c;真是&#xff0c;哎 各个uwsgi配置应该怎样设置项目路径 django项目1中创建的django项目名为 web 那么uwsgi.ini中要设置为 chdir …

【Vue3 ts】echars图表展示统计的月份数据

图片展示 此处内容为展示24年各个月份产品的创建数量。在后端统计24年各个月份产品数量后&#xff0c;以数组的格式发送给前端&#xff0c;前端负责展示。 后端 entity层&#xff1a; Data Schema(description "月份统计")public class MonthCount {private Stri…

得物六宫格验证码分析

声明(lianxi a15018601872) 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 前言(lianxi a…