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 替代方案

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

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

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

结语

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

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

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

相关文章

通讯录管理(C++入门练习)

通讯录管理系统 系统需求: 通讯录是一个可以记录亲人、好友信息的工具。本文主要利用C来实现一个通讯录管理系统系统中需要实现的功能如下: 添加联系人:向通讯录中添加新人,信息包括(姓名、性别、年龄、联系电话、家庭住址)最多记录1000人 显示联系…

昇思25天学习打卡营第21天|DCGAN生成漫画头像

DCGAN原理 DCGAN(深度卷积对抗生成网络,Deep Convolutional Generative Adversarial Networks)是GAN的直接扩展。不同之处在于,DCGAN会分别在判别器和生成器中使用卷积和转置卷积层。 它最早由Radford等人在论文Unsupervised Re…

数据结构历年考研真题对应知识点(哈夫曼树和哈夫曼编码)

目录 5.5.1哈夫曼树和哈夫曼编码 1.哈夫曼树的定义 2.哈夫曼树的构造 【分析哈夫曼树的路径上权值序列的合法性(2010)】 【哈夫曼树的性质(2010、2019)】 3.哈夫曼编码 【根据哈夫曼编码对编码序列进行译码(201…

【AMBA】AXI总线中的AXLEN、AXSIZE、AXBURST和4K边界

文章目录 AXI传输层级概念AXLEN[7:0]定义突发传输长度AXSIZE[2:0]定义突发传输transfer的位宽AXBURST[1:0]定义突发传输类型4K边界 AXI传输层级概念 在手册的术语表中,与 AXI 传输相关的有三个概念,分别是 transfer(beat)、burst、transaction。用一句话…

树莓派关机

文件 shutdown.sh #!/usr/bin/bash sudo shutdown -r nowpython 文件开头添加 #!/usr/bin/python3

C字符串和内存函数介绍(三)——其他的字符串函数

在#include<string.h>的这个头文件里面&#xff0c;除了前面给大家介绍的两大类——长度固定的字符串函数和长度不固定的字符串函数。还有一些函数以其独特的用途占据一席之地。 今天要给大家介绍的是下面这三个字符串函数&#xff1a;strstr&#xff0c;strtok&#xf…

前端web性能统计

前端web性能统计 1. 背景2. 业界方案2.1 腾讯2.2 蚂蚁金服2.3 字节跳动2.4 美团 3. 相关观念3.1 RAIL模型3.2 性能指标3.3 真实用户监控3.4 performance 4. 性能监控工具介绍5. 推荐采用方案 1. 背景 在如今的数字时代&#xff0c;网站和应用程序的性能对用户体验至关重要。用…

STM32MP135裸机编程:唯一ID(UID)、设备标识号、设备版本

0 资料准备 1.STM32MP13xx参考手册1 唯一ID&#xff08;UID&#xff09;、设备标识号、设备版本 1.1 寄存器说明 &#xff08;1&#xff09;唯一ID 唯一ID可以用于生成USB序列号或者为其它应用所使用&#xff08;例如程序加密&#xff09;。 &#xff08;2&#xff09;设备…

Git代码管理工具 — 3 Git基本操作指令详解

目录 1 获取本地仓库 2 基础操作指令 2.1 基础操作指令框架 2.2 git status查看修改的状态 2.3 git add添加工作区到暂存区 2.4 提交暂存区到本地仓库 2.5 git log查看提交日志 2.6 git reflog查看已经删除的记录 2.7 git reset版本回退 2.8 添加文件至忽略列表 1 获…

0.单片机工作原理

文章目录 最小系统 单片机芯片 时钟电路 复位电路 电源 最小系统 单片机芯片 本次51单片机的芯片为&#xff1a;STC89C52 Flash(闪存)程序存储器&#xff1a;存储程序的空间 SRAM&#xff1a;数据存储器&#xff0c;可用于存放程序执行的中间结果和过程数据 DPTR&#xff1a;…

2024-07-14 Unity插件 Odin Inspector2 —— Essential Attributes

文章目录 1 说明2 重要特性2.1 AssetsOnly / SceneObjectsOnly2.2 CustomValueDrawer2.3 OnValueChanged2.4 DetailedInfoBox2.5 EnableGUI2.6 GUIColor2.7 HideLabel2.8 PropertyOrder2.9 PropertySpace2.10 ReadOnly2.11 Required2.12 RequiredIn&#xff08;*&#xff09;2.…

机器人相关工科专业课程体系

机器人相关工科专业课程体系 前言传统工科专业机械工程自动化/控制工程计算机科学与技术 新兴工科专业智能制造人工智能机器人工程 总结Reference: 前言 机器人工程专业是一个多领域交叉的前沿学科&#xff0c;涉及自然科学、工程技术、社会科学、人文科学等相关学科的理论、方…

Linux编程(三)—makefile快速编译

起因 linux环境下&#xff0c;编译c程序很麻烦&#xff0c;后面g -o demo demo.cpp ……往往跟了许多许多东西&#xff0c;这些每次编译的时候都要书写&#xff0c;所以就产生了makefile快速编译方式&#xff0c;具体操作如下。 怎么用makefile? 第一步&#xff1a;下载 m…

WPF学习(2) -- 样式基础

一、代码 <Window x:Class"学习.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d"http://schemas.microsoft.com/expression/blend/2008&…

MySQL 聚簇索引和非聚簇索引有什么区别?

聚簇索引&#xff08;主键索引&#xff09;、非聚簇索引&#xff08;二级索引&#xff09;。 这两者之间的最主要的区别是 B 树的叶子节点存放的内容不同&#xff1a; 聚簇索引的 B 树叶子节点存放的是主键值完整的记录&#xff1b;非聚簇索引的 B 树叶子节点存放的是索引值主…

【面试八股总结】C++内存管理:内存分区、内存泄漏、new和delete、malloc和free

参考资料&#xff1a;代码随想录、阿秀 一、内存分区 &#xff08;1&#xff09;栈区 在执行函数时&#xff0c;函数内部局部变量的存储单元都可以在栈上创建&#xff0c;函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中&#xff0c;效率很高&am…

Postman下载及使用说明

Postman使用说明 Postman是什么&#xff1f; ​ Postman是一款接口对接工具【接口测试工具】 接口&#xff08;前端接口&#xff09;是什么&#xff1f; ​ 前端发送的请求普遍被称为接口 ​ 通常有网页的uri参数格式json/key-value请求方式post/get响应请求的格式json 接…

关闭Ubuntu烦人的apport

先来看让人绷不住的&#xff08;恼&#xff09; 我查半天apport是啥玩意发现就一错误报告弹窗&#xff0c;十秒钟给我弹一次一天给我内存弹爆了 就算我程序就算真的不停崩溃&#xff0c;也没你这傻比apport杀伤性强啊&#xff1f;&#xff1f;&#xff1f; 原则上是不建议关闭…

牛客周赛 Round 51 解题报告 | 珂学家

前言 题解 典题场&#xff0c; EF都有很多种解法 A. 小红的同余 性质: 相邻两数互质 x ( m 1 ) / 2 x (m1)/2 x(m1)/2 m int(input())print ((m 1) // 2)B. 小红的三倍数 性质: 各个位数之和是3的倍数&#xff0c;可被3整除 和数的组合顺序无关 n int(input()) arr…

MySQL高级面试点

Explain语句结果中各个字段分别代表什么 id&#xff1a;查询语句没出现一个select关键字&#xff0c;MySQL就会给他分配一个唯一id select_type&#xff1a; select关键字对应哪个查询的类型 simple&#xff1a;简单的查询 不包含任何子查询 primary&#xff1a;查询中如果…