当前位置: 首页 > news >正文

23种设计模式——单例模式(Singleton)​详解

 ✅作者简介:大家好,我是 Meteors., 向往着更加简洁高效的代码写法与编程方式,持续分享Java技术内容。
🍎个人主页:Meteors.的博客
💞当前专栏: 设计模式
特色专栏: 知识分享
🥭本文内容: 23种设计模式——单例模式(Singleton)​详解
📚 ** ps **  : 阅读文章如果有问题或者疑惑,欢迎在评论区提问或指出。


目录

一. 背景

二. 单例模式介绍

三. 单例模式使用场景

四. 单例模式实现方式

方式一:饿汉式(Eager Initialization)

方式二:懒汉式(Lazy Initialization)

方式三:枚举(Enum)

五. 各种实现方式对比


一. 背景

单例模式是项目中很常用的设计模式。在项目的配置管理器中经常使用,负责管理应用的全局配置信息。通过单例模式可以确保整个应用中只有一个配置管理器实例,统一管理所有配置。

二. 单例模式介绍

单例模式是一种​​创建型设计模式​​,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。

它的核心思想是:​​控制实例的数量,节约系统资源​​。


三. 单例模式使用场景

在许多场景下,我们只需要一个对象来完成全局性的工作,创建多个实例不仅没有必要,还会浪费资源,甚至导致程序行为异常。例如:

  • ​配置信息类​​:整个应用共享一份配置,读取和修改都通过同一个对象进行。

  • ​日志记录类​​:所有日志都通过一个日志器写入同一个文件,避免多实例操作文件导致内容错乱。

  • ​数据库连接池​​:管理数据库连接,需要全局唯一以便高效管理连接资源。

  • ​线程池​​:类似数据库连接池。

  • ​缓存系统​​:如 Redis 客户端,通常一个应用一个实例就够了。

  • ​工具类​​:一些只提供静态方法,没有自身状态的工具类,也常被设计为单例。

如果不使用单例模式,而是随意创建实例,可能会导致:

  • ​资源浪费​​:频繁创建和销毁对象开销大。

  • ​数据不一致​​:多个实例可能持有不同的状态(例如,配置被一个实例修改,另一个实例却不知道)。

  • ​程序错误​​:例如多个日志实例同时写一个文件,会导致日志内容混乱。


四. 单例模式实现方式

实现一个单例模式通常需要注意以下三点:

  1. ​私有化构造方法​​:防止外部通过 new关键字创建实例。

  2. ​内部创建并持有该私有静态实例​​:在类内部自己创建这个唯一的实例。

  3. ​提供一个公共的静态方法​​:供外部获取这个唯一的实例。

根据实例创建的时机,主要分为两种模式:​​饿汉式​​和​​懒汉式​​。

方式一:饿汉式(Eager Initialization)

类加载时就直接初始化实例。​​简单、线程安全,但可能造成资源浪费​​(如果实例一直没被用到)。

public class EagerSingleton {// 1. 在类加载时就创建好实例private static final EagerSingleton INSTANCE = new EagerSingleton();// 2. 私有化构造函数private EagerSingleton() {}// 3. 提供全局访问点public static EagerSingleton getInstance() {return INSTANCE;}
}

​优点​​:实现简单,线程安全(由 JVM 类加载机制保证)。

​缺点​​:如果实例很大且从未使用,会造成内存浪费。


方式二:懒汉式(Lazy Initialization)

延迟加载,只有在第一次被调用时才创建实例。

​a) 基础版本(线程不安全)​

多线程环境下可能创建多个实例。

public class UnsafeLazySingleton {private static UnsafeLazySingleton instance;private UnsafeLazySingleton() {}public static UnsafeLazySingleton getInstance() {// 如果实例不存在,则创建if (instance == null) {instance = new UnsafeLazySingleton();}return instance;}
}

​b) 同步方法版(线程安全但效率低)​

通过 synchronized加锁保证线程安全,但每次获取实例都要同步,性能差。

public class SynchronizedLazySingleton {private static SynchronizedLazySingleton instance;private SynchronizedLazySingleton() {}// 使用 synchronized 关键字修饰方法public static synchronized SynchronizedLazySingleton getInstance() {if (instance == null) {instance = new SynchronizedLazySingleton();}return instance;}
}

​c) 双重校验锁(DCL, Double-Checked Locking)​

​推荐写法​​。在加锁前后进行两次检查,兼顾线程安全和性能。

public class DCLSingleton {// 使用 volatile 关键字禁止指令重排序,保证可见性private static volatile DCLSingleton instance;private DCLSingleton() {}public static DCLSingleton getInstance() {// 第一次检查,避免不必要的同步if (instance == null) {// 同步代码块synchronized (DCLSingleton.class) {// 第二次检查,确保实例在同步块内未被创建if (instance == null) {instance = new DCLSingleton();}}}return instance;}
}

volatile关键字在这里至关重要,它防止了 new DCLSingleton()这一步的指令重排序,避免了其他线程获取到一个未初始化完成的对象。

​d) 静态内部类(Holder Class)​

​最优雅、最推荐的实现方式之一​​。利用 JVM 的类加载机制保证线程安全,同时实现了懒加载。

public class InnerClassSingleton {// 私有化构造方法private InnerClassSingleton() {}// 静态内部类持有实例private static class SingletonHolder {private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();}// 调用 getInstance 时,才会加载 SingletonHolder 类,从而初始化 INSTANCEpublic static InnerClassSingleton getInstance() {return SingletonHolder.INSTANCE;}
}

​优点​​:

  • ​懒加载​​:只有在调用 getInstance()时,内部类 SingletonHolder才会被加载,实例才会被创建。

  • ​线程安全​​:由 JVM 在类加载时完成初始化,天然线程安全。

  • ​实现简单​​:无需同步代码块,代码简洁。


方式三:枚举(Enum)

​《Effective Java》作者 Josh Bloch 强烈推荐的方式​​。它不仅能避免多线程同步问题,还能防止反序列化重新创建新的对象。

public enum EnumSingleton {INSTANCE; // 唯一的实例// 可以添加任意方法public void doSomething() {System.out.println("Doing something by " + this.toString());}
}// 使用方式
EnumSingleton.INSTANCE.doSomething();

​优点​​:

  • ​绝对防止多实例​​:由 JVM 从根本上保证。

  • ​防止反射攻击​​:枚举类不能通过反射创建实例。

  • ​防止反序列化​​:枚举类在反序列化时不会创建新对象。

  • ​代码极简​​。

​缺点​​:不够灵活(例如无法实现延迟初始化)。


五. 各种实现方式对比

实现方式

懒加载

线程安全

性能

防反射/反序列化

推荐度

​饿汉式​

⭐⭐

​同步方法懒汉式​

​双重校验锁(DCL)​

⭐⭐⭐⭐

​静态内部类​

⭐⭐⭐⭐⭐

​枚举​

⭐⭐⭐⭐⭐

​选择?:

  • 如果对内存不敏感,追求极致的简单,可以用​​饿汉式​​。

  • 如果需要懒加载,且是现代 Java 开发,​​静态内部类​​是最佳选择,简单又安全。

  • 如果需要防御高级的攻击(如反射、反序列化),或者实现一个表示状态的单例,​​枚举​​是最佳选择。

  • ​双重校验锁​​稍微复杂,但在一些特定场景(如需要延迟初始化且实例字段需要延迟初始化时)仍有其价值。

当然,单例模式有时也会限制之后相关类的异步实现,使用前要仔细考虑!


最后,

        其它设计模式会陆续更新,希望文章对你有所帮助!

http://www.dtcms.com/a/353173.html

相关文章:

  • 金仓数据库文档系统全面升级:用户体验焕然一新
  • CPU、IO、网络与内核参数调优
  • Linux 性能调优实战:CPU、磁盘 I/O、网络与内核参数
  • 系统架构设计师备考第8天——嵌入式系统
  • 工业网络安全:保护制造系统和数据
  • Linux 系统CPU-IO-网络-内核参数的调优
  • 【学习笔记】GB 42250-2022标准解析
  • 手写MyBatis第36弹:MyBatis执行流程中SQL命令类型解析
  • Effective c++ 35条款详解
  • docker run 后报错/bin/bash: /bin/bash: cannot execute binary file总结
  • Python计算点云的欧式、马氏、最近邻、平均、倒角距离(Chamfer Distance)
  • iOS技术之通过Charles抓包http、https数据
  • 【开题答辩全过程】以Trlig(服装网站)为例,包含答辩的问题和答案
  • ETH PPS 配置链路
  • 车载诊断架构 --- 基于整车功能的正向诊断需求开发
  • Ruoyi-cloud 微服务部署双方案:本地与 K8S 实践手册
  • FastAPI + SQLModel 从 0 搭到完整 CRUD
  • 腾讯云人脸库技术架构深度解析
  • Github 3k+ star,中后台管理系统框架,支持多款 UI 组件库,兼容PC、移动端!比商业系统还专业!!
  • IntelliJ IDEA Debug 模式功能指南
  • 微算法科技(NASDAQ:MLGO)突破性FPGA仿真算法技术助力Grover搜索,显著提升量子计算仿真效率
  • 【数据结构】树和二叉树——树和森林
  • Python音频分析与线性回归:探索声音中的数学之美
  • 基于 Qt 实现的动态流程图画板框架设计与实现
  • 储能变流器学习之MPPT
  • 教程:按年份导出中国县级 NDVI(月均值 CSV)
  • 【87页PPT】新能源汽车解决方案(附下载方式)
  • 把 AI 塞进「盲文点显器」——基于触觉反馈的离线双向翻译笔
  • 【RAG】使用llamaindex进行RAG开发
  • 【前端】Devtools使用