重读 Java 设计模式: 解析单例模式,保证唯一实例的创建与应用

本周工作太忙了,变成了加班狗,下班回来也没时间写,只能利用周末时间写了😭。

好了,言归正传,本次我们先来介绍下设计模式中创建型模式-单例模式

一、引言

单例模式是设计模式中最简单但又最常用的一种模式之一。它确保某个类只能有一个实例,并提供了全局访问点,使得该实例在整个应用程序中都可以被访问。在本文中,我们将深入探讨单例模式的实现方式、应用场景以及实践指南。

二、基本概念

除了引言一段中,单例模式除了实例全局唯一全局访问点外,还有其他特点,下面我将一一罗列出来:

  • 实例全局唯一:单例模式确保一个类只有一个实例对象存在,无论在何处访问该类,都将得到相同的实例。通过单例模式,可以防止不必要的多次实例化,确保系统中某个类只有一个实例存在。
  • 全局访问点:单例模式提供了一个全局的访问点,任何地方都可以通过该访问点获取到单例实例。
  • 延迟加载:许多单例模式的实现方式采用了延迟加载的策略,即在需要时才创建实例对象,而不是在类加载时就创建。
  • 线程安全:好的单例模式实现应该是线程安全的,即在多线程环境下也能够正确地保持单例对象的唯一性。
  • 节约资源:单例模式可以节约系统资源,特别是那些需要频繁创建和销毁的对象,如数据库连接、线程池等。
  • 全局状态管理:单例模式可以用于管理全局的状态信息,例如配置管理器、日志系统等

三、单例模式的五种经典实现案例

3.1 饿汉式单例模式

优点:
  • 线程安全:在类加载时就创建实例对象,因此不存在多线程环境下的线程安全问题。
  • 简单易理解:实现简单,易于理解和使用。
缺点:
  • 不支持延迟加载:在类加载时就创建实例对象,可能会导致资源浪费,尤其是在实例对象较大或者初始化过程较为复杂的情况下。
package com.markus.desgin.mode.creational.singleton;/*** @Author: zhangchenglong06* @Date: 2024/3/6* @Description: 单例模式一:急切初始化*/
public class EagerlyInitSingleton {// 利用 类加载机制中 static 属性加载特性:全局只加载一遍,不会随实例的初始化重复加载private static EagerlyInitSingleton INSTANCE = new EagerlyInitSingleton();public static EagerlyInitSingleton getInstance() {return INSTANCE;}public static void main(String[] args) {EagerlyInitSingleton eagerlyInitSingleton1 = getInstance();EagerlyInitSingleton eagerlyInitSingleton2 = getInstance();System.out.println(eagerlyInitSingleton1 == eagerlyInitSingleton2);}
}

3.2 懒汉式单例模式

优点:
  • 延迟加载:只有在需要时才创建实例对象,节省了系统资源。
  • 简单易理解:实现简单,易于理解和使用。
缺点:
  • 线程不安全:在多线程环境下,如果多个线程同时调用获取实例的方法,可能会创建多个实例对象,导致单例模式失效。
  • 性能低下:由于使用了 synchronized 关键字进行同步,导致性能较低。
package com.markus.desgin.mode.creational.singleton;/*** @Author: zhangchenglong06* @Date: 2024/3/6* @Description: 单例模式二: 懒加载模式*/
public class LazyInitSingleton {private static LazyInitSingleton INSTANCE = null;public static synchronized LazyInitSingleton getInstance() {if (INSTANCE == null) {INSTANCE = new LazyInitSingleton();}return INSTANCE;}public static void main(String[] args) {LazyInitSingleton singleton1 = getInstance();LazyInitSingleton singleton2 = getInstance();System.out.println(singleton1 == singleton2);}
}

3.3 双重检验锁单例模式

优点:
  • 线程安全:通过双重检验锁机制来确保只有一个实例对象被创建,解决了懒汉式单例模式的线程安全问题。
  • 延迟加载:只有在需要时才创建实例对象,节省了系统资源。
缺点:
  • 实现复杂:双重检验锁的实现较为复杂,需要注意双重检验锁中的原子性问题。
package com.markus.desgin.mode.creational.singleton;import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;/*** @Author: zhangchenglong06* @Date: 2024/3/6* @Description:*/
public class DoubleCheckLockSingleton {// volatile 加入内存屏障机制,将 实例对象的初始化过程 顺序保证原子性private static volatile DoubleCheckLockSingleton INSTANCE;public static DoubleCheckLockSingleton getInstance() {if (INSTANCE == null) {// 局部加锁,相比懒汉式,性能更优synchronized (DoubleCheckLockSingleton.class) {if (INSTANCE == null) {// 这个阶段可能会由于 JVM 的性能优化,内部字节码出现乱序执行的情况,所以加上 volatile 关键字INSTANCE = new DoubleCheckLockSingleton();}}}return INSTANCE;}public static void main(String[] args) throws ExecutionException, InterruptedException {DoubleCheckLockSingleton singleton1 = getInstance();DoubleCheckLockSingleton singleton2 = getInstance();System.out.println(singleton1 == singleton2);CompletableFuture<DoubleCheckLockSingleton> future1 = CompletableFuture.supplyAsync(DoubleCheckLockSingleton::getInstance);CompletableFuture<DoubleCheckLockSingleton> future2 = CompletableFuture.supplyAsync(DoubleCheckLockSingleton::getInstance);singleton1 = future1.get();singleton2 = future2.get();System.out.println(singleton1 == singleton2);}
}

3.4 静态内部类单例模式

优点:
  • 线程安全:利用类加载机制保证了静态内部类只会被加载一次,从而保证了单例的线程安全性。
  • 延迟加载:只有在需要时才会加载静态内部类,从而实现了延迟加载。
缺点:
  • 不支持传参:静态内部类方式不能支持传递参数给单例的构造函数,因为静态内部类的实例化是由 JVM 保证线程安全的,不能传递参数。
package com.markus.desgin.mode.creational.singleton;import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;/*** @Author: zhangchenglong06* @Date: 2024/3/6* @Description: 单例模式四: 静态内部类*/
public class StaticInnerClassSingleton {private static class Singleton {public static StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();}private StaticInnerClassSingleton() {}public static StaticInnerClassSingleton getInstance() {return Singleton.INSTANCE;}public static void main(String[] args) throws ExecutionException, InterruptedException {StaticInnerClassSingleton singleton1 = getInstance();StaticInnerClassSingleton singleton2 = getInstance();System.out.println(singleton1 == singleton2);CompletableFuture<StaticInnerClassSingleton> future1 = CompletableFuture.supplyAsync(StaticInnerClassSingleton::getInstance);CompletableFuture<StaticInnerClassSingleton> future2 = CompletableFuture.supplyAsync(StaticInnerClassSingleton::getInstance);singleton1 = future1.get();singleton2 = future2.get();System.out.println(singleton1 == singleton2);}
}

4.4 枚举单例模式

优点:
  • 线程安全:枚举类型是线程安全的,并且只会装载一次,从而保证了单例的线程安全性。
  • 防止反射攻击:枚举类型在反序列化时会检查是否为枚举,从而防止了反射攻击。
缺点:
  • 不支持延迟加载:枚举类型是在类加载时就实例化的,无法实现延迟加载,可能会导致资源浪
package com.markus.desgin.mode.creational.singleton;/*** @Author: zhangchenglong06* @Date: 2024/3/6* @Description: 单例模式五: 枚举*/
public enum EnumSingleton {INSTANCE;public EnumSingleton getInstance(){return INSTANCE;}
}

单例模式在 Spring 中的应用场景

在 Spring 框架中,所有被 Spring 管理的 Bean 默认都是单例的。Spring 容器负责创建这些 Bean 的唯一实例,并在需要时注入到其他 Bean 中。

例如:

  • 业务 Spring Bean:这种一般是用户自定义的实例,将其定义到 Spring 框架中,并由 Spring 容器进行管理
  • 框架基础设施 Bean:
    • 资源加载器(ResourceLoader):Spring 框架提供了资源加载器来加载各种资源,如配置文件、静态文件等。资源加载器通常也是单例的,在整个应用程序中只存在一个实例,负责加载和管理资源。
    • 事件广播器(ApplicationEventPublisher):Spring 框架提供了事件机制来支持应用程序中的事件处理,而事件发布器用于发布事件通知给感兴趣的监听器。事件发布器通常也是单例的,在整个应用程序中只存在一个实例,负责发布事件通知。
    • 数据源(DataSource):在 Spring 中,数据源用于连接数据库,并提供了对数据库的访问。通常情况下,数据源也是单例的,在整个应用程序中只存在一个实例,负责管理数据库连接。
    • 事务管理器(PlatformTransactionManager):Spring 框架提供了事务管理器来管理事务的提交和回滚。事务管理器通常也是单例的,在整个应用程序中只存在一个实例,负责管理事务的生命周期。
    • 请求分发(DispatcherServlet):DispatcherServlet是Spring MVC框架中的前端控制器,负责接收HTTP请求并将请求分发给相应的控制器进行处理。在Spring MVC中,通常会配置一个全局的DispatcherServlet实例,以确保整个应用程序中只有一个DispatcherServlet实例。
    • 等等…

还有一些其他使用场景,例如 DefaultBeanNameGenerator、SimpleAutowireCandidateResolver 等等。

image-20240310165437362

image-20240310165537876

实践指南

在实践中,我们应该注意以下几点:

  • 线程安全性:在多线程环境下,要确保单例模式的实现是线程安全的,可以考虑使用双重检查锁或者静态内部类的方式来实现单例。
  • 懒加载与饿加载:根据项目的实际需求,选择合适的单例实现方式,是懒加载还是饿加载。
  • 防止反射和序列化攻击:考虑通过私有构造函数、枚举类型等方式来防止反射和序列化攻击。

本文总结

好了,总结一下:

本文详细介绍了单例模式在设计模式中的重要性以及在Spring框架中的应用场景。首先,我们从单例模式的基本概念出发,介绍了其特点和优势,包括全局唯一实例、延迟加载、线程安全等。然后,我们深入讨论了单例模式的五种经典实现案例,包括饿汉式、懒汉式、双重检验锁、静态内部类和枚举方式,每种实现方式的优缺点都进行了详细分析和比较。接着,我们探讨了单例模式在Spring框架中的应用场景,涵盖了Bean管理、IoC容器、AOP代理等各个方面。最后,我们给出了一些实践指南,帮助读者在实际项目中正确使用单例模式。

综上所述,单例模式作为一种简单但又非常重要的设计模式,在实际开发中有着广泛的应用,特别是在Spring框架等大型应用程序中。通过深入理解和正确应用单例模式,可以有效提高系统的性能和可维护性,是每个Java开发者都应该掌握的重要知识点。

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

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

相关文章

springboot260火锅店管理系统

火锅店管理系统设计与实现 摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装火锅店管理系统软件来发挥其高效…

Cloud-Sleuth分布式链路追踪(服务跟踪)

简介 在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的服务节点调用来协同产生最后的请求结果,每一个前端请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败 GitHub - spring-cloud/spring-cloud-sl…

【数据集】waymo motion dataset介绍与数据解析可视化

文章目录 前言1. 数据下载2. Motion数据集3. 数据描述4. 数据特征scenario.proto5. 地图特征map.proto6. 数据解析6.1 数据解析过程6.1 数据解析过程报错7. 数据可视化前言 waymo open dataset数据集由两个数据集组成: Perception数据集 + Motion数据集 Perception数据集主要用…

后端项目访问不了

问题&#xff1a; 后端启动不了&#xff0c;无法访问网站 原因&#xff1a; 1.防火墙没有关 2.有缓存 3、项目没有启动 4、docker没有启动 解决&#xff1a; 先查看进程&#xff1a;docker ps&#xff0c;必须有三个 详细查看&#xff1a;docker ps -a exited代表没有开启…

基于深度视觉实现机械臂对目标的识别与定位

机械臂手眼标定 根据相机和机械臂的安装方式不同&#xff0c;手眼标定分为眼在手上和眼在手外两种方式&#xff0c;双臂机器人的相机和机械臂基座的相对位置固定&#xff0c;所以应该采用眼在手外的手眼标定方式。 后续的视觉引导机械臂抓取测试实验基于本实验实现&#xf…

HTTP有什么缺陷,HTTPS是怎么解决的

缺陷 HTTP是明文的&#xff0c;谁都能看得懂&#xff0c;HTTPS是加了TLS/SSL加密的&#xff0c;这样就不容易被拦截和攻击了。 SSL是TLS的前身&#xff0c;他俩都是加密安全协议。前者大部分浏览器都不支持了&#xff0c;后者现在用的多。 对称加密 通信双方握有加密解密算法…

活体检测(点头,摇头,张嘴等动态识别)

活体检测&#xff08;点头&#xff0c;摇头&#xff0c;张嘴等动态识别&#xff09; 某本书里有一句话&#xff0c;等我去读、去拍案。 田间的野老&#xff0c;等我去了解、去惊识。 山风与发&#xff0c;冷泉与舌&#xff0c; 流云与眼&#xff0c;松涛与耳&#xff0c; 他们等…

吴恩达机器学习笔记十六 如何debug一个学习算法 模型评估 模型选择和训练 交叉验证测试集

如果算法预测出的结果不太好&#xff0c;可以考虑以下几个方面&#xff1a; 获得更多的训练样本 采用更少的特征 尝试获取更多的特征 增加多项式特征 增大或减小 λ 模型评估(evaluate model) 例如房价预测&#xff0c;用五个数据训练出的模型能很好的拟合这几个数据&am…

java正则表达式概述及案例

前言&#xff1a; 学习了正则表达式&#xff0c;记录下使用心得。打好基础&#xff0c;daydayup! 正则表达式 什么是正则表达式 正则表达式由一些特定的字符组成&#xff0c;代表一个规则。 正则表达式的功能 1&#xff1a;用来校验数据格式是否合规 2&#xff1a;在一段文本…

QT画图功能

QT画图功能 每个QWidget都自带的功能&#xff0c;继承了QPainteDevice都可以使用QPainter来进行绘图。 画图需要调用paintEvent绘制事件&#xff0c;paintEvent事件时QWidget类自带的事件。 重写paintEvent事件。&#xff08;重写事件&#xff1a;如果父类有某个方法&#xff…

C++类和对象(下篇)

目录 一.再谈构造函数 二.static成员 三.友元 四.内部类 五. 再次理解类和对象 一.再谈构造函数 1.构造函数体赋值 在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给对象中各个成员变量一个合适的初始值。 class Date { public:Date(int year, int month…

8.5 Springboot项目实战 Redis缓存热点数据

文章目录 前言一、缓存与数据库一致性二、Repository层 -- Cache Aside模式实操BookRepositoryBookRepositoryImpl2.1 查询2.2 修改2.3 删除2.4 扩展Mapper修改三、Service层调用修改四、测试前言 前两文我们讲解了如何在SpringBoot中整合Redis,接下来我们将进行