Java设计模式之单例模式(多种实现方式)

news/2024/5/10 14:17:18

虽然写了很多年代码,但是说真的对设计模式不是很熟练,虽然平时也会用到一些,但是都没有深入研究过,所以趁现在有空练下手

这章主要讲单例模式,也是最简单的一种模式,但是因为spring中bean的广泛应用,所以现在单例模式在应用中其实很少会手动实现

在Spring中,默认情况下,Bean 是单例的,这意味着 Spring 容器会在第一次请求该 Bean 时创建一个实例,并且在整个应用程序的生命周期中保持该实例的单一性。换句话说,每次从 Spring 容器中请求相同的 Bean 时,都会得到相同的实例。

单例模式是一种常见的设计模式,适用于以下情况:

1.资源共享:当系统中需要共享某个资源(如数据库连接池、线程池、配置信息等)的时候,可以使用单例模式确保全局只有一个实例,避免资源的重复创建和浪费。

2.对象缓存:在需要频繁创建和销毁对象的情况下,可以使用单例模式将对象缓存起来,提高性能。

3.线程池:线程池通常被设计为单例,以确保在整个应用程序中只有一个线程池实例,用于管理线程的生命周期和执行任务。

4.日志对象:在系统中使用单例模式创建日志对象,可以确保所有的日志信息被统一记录,避免出现混乱的日志信息。

5.配置文件对象:将系统中的配置信息封装到单例对象中,可以方便地进行读取和修改。

6.对话框、窗口等界面组件:在图形用户界面(GUI)程序中,通常只需要一个对话框或窗口实例,可以使用单例模式确保全局只有一个实例。

7.管理器类:例如线程管理器、事件管理器等,这些管理器类通常被设计为单例,以便在整个系统中统一管理资源和事件。

总之,任何需要在系统中全局唯一存在的对象,且需要被频繁访问和共享的情况下,都可以考虑使用单例模式。

首先是最简单实用的饿汉模式

优点:

1.线程安全: 饿汉模式在类加载时就创建实例,并且实例是静态的 final 变量,因此在多线程环境下是线程安全的,不需要额外的线程同步控制。

2.简单易用: 饿汉模式的实现非常简单,通过静态变量初始化的方式就可以保证实例的唯一性和全局可访问性,不需要复杂的代码结构。

3.无需考虑懒加载和线程安全问题: 由于实例是在类加载时就创建好的,所以不需要考虑懒加载和线程安全问题,避免了相关的复杂性。

4.性能较好: 因为实例是在类加载时就创建好的,所以在获取实例时无需进行额外的判断和同步操作,性能较好。

缺点:

1.资源浪费: 饿汉模式在应用程序启动时就创建实例,并且实例是在整个应用程序生命周期内存在的,可能会导致资源的浪费。特别是如果实例占用大量资源或者需要较长时间进行初始化,可能会影响应用程序的启动速度。

2.不支持延迟加载: 饿汉模式不支持延迟加载,因为实例是在类加载时就创建好的,无法根据需要进行延迟加载。

3.可能导致类加载较慢: 如果一个类的实例创建比较耗时,那么在类加载时就会导致类加载较慢,影响整个应用程序的启动速度。

综上所述,饿汉模式适用于对性能要求较高,且实例创建比较简单且资源消耗较小的情况下。但是需要注意可能存在的资源浪费问题,特别是对于大型对象或者需要耗时初始化的实例。

直接上代码(建议使用)

public class EagerSingleton {// 在类加载时就创建实例,并初始化为静态变量private static final EagerSingleton instance = new EagerSingleton();// 私有化构造方法,防止外部实例化private EagerSingleton() {}// 获取单例实例的方法public static EagerSingleton getInstance() {try {// 模拟处理业务逻辑耗时Thread.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}return instance;}public static void main(String[] args) {for (int i= 0; i < 100; i++) {new Thread(() -> {System.out.println(EagerSingleton.getInstance().hashCode());}).start();}}
}

执行一下
可以看到hashCode都是一样的,这里有人可能会说hashCode一样并不代表对象一样,我只能说你的确是对的,但这不在本章讲解范围内

在这里插入图片描述

然后我们再来看一下懒汉模式

优点:

1.延迟加载(Lazy Loading): 懒汉模式在首次访问时才会创建实例,避免了在程序启动时就创建对象实例,节省了内存和系统资源。

2.节省资源: 因为实例是在需要时才创建的,所以在大部分情况下不会占用额外的资源。

3.线程安全问题相对简单: 在单线程环境下,懒汉模式不需要额外的线程同步机制来保证线程安全,实现简单。

缺点:

1.线程安全性问题: 在多线程环境下,懒汉模式可能存在线程安全问题。当多个线程同时调用 getInstance() 方法时,如果没有进行额外的线程同步处理,可能会导致创建多个实例。

2.性能问题: 在并发环境下,由于需要额外的线程同步控制,懒汉模式的性能可能会受到一定影响。例如,使用双重检查锁(Double-Checked Locking)来确保线程安全性,会增加额外的开销。

3.可能存在反序列化问题: 当类实现了 Serializable 接口,并且对象被序列化然后再反序列化时,如果没有正确地处理单例对象,可能会破坏单例的约束,导致出现多个实例。

4.不适用于高并发场景: 在高并发场景下,频繁调用 getInstance() 方法可能会导致性能瓶颈,因为所有线程都需要竞争同一个锁来获取实例。

综上所述,懒汉模式适用于单线程环境或者对性能要求不是非常高的场景,但在多线程环境下需要特别注意线程安全性问题,并且需要针对性能做出权衡。

直接上代码(不建议使用)

public class LazySingleton {private static LazySingleton instance;// 私有化构造方法,防止外部实例化private LazySingleton() {}// 获取单例实例的方法public static LazySingleton getInstance() {// 在第一次调用时才创建实例if (instance == null) {try {// 模拟处理业务逻辑耗时Thread.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}instance = new LazySingleton();}return instance;}public static void main(String[] args) {for (int i= 0; i < 100; i++) {new Thread(() -> {System.out.println(LazySingleton.getInstance().hashCode());}).start();}}
}

执行一下
发现没有,hashCode都不一样,虽然hashCode相同不能证明对象是同一个,但是hashCode不相同肯定不是同一个对象,这说明其实是线程不安全的,因此这种写法其实是被淘汰了的

在这里插入图片描述

上面的那种写法虽然不推荐使用,但是提供了一种思路,就是只在需要的时候才加载,其主要目的还是为了节省资源(现在的硬件其实都很强大,这点资源省不省问题其实不大,这也让我想起了很多年前我刚入行还在写C++,当时问我师父说这个指针要是忘了释放怎么办,他跟我说没关系的,现在电脑都很牛逼,这点资源浪费根本影响不了什么)

好了下面我们来完善一下懒汉模式,最简单的方法就是使用synchronized关键字来保证线程的安全,当然同时也就伴随着性能的损耗(不推荐使用)
这里直接用synchronized关键字锁整个方法

public class LazySingleton {// 注意:volatile关键字是必须的,防止指令重排序private static volatile LazySingleton instance;// 私有化构造方法,防止外部实例化private LazySingleton() {}// 获取单例实例的方法public static synchronized LazySingleton getInstance() {// 在第一次调用时才创建实例if (instance == null) {try {// 模拟处理业务逻辑耗时Thread.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}instance = new LazySingleton();}return instance;}public static void main(String[] args) {for (int i= 0; i < 100; i++) {new Thread(() -> {System.out.println(LazySingleton.getInstance().hashCode());}).start();}}
}

执行一下

在这里插入图片描述

上面那个示例虽然保证了一个实例,但是性能上还是不如意,如是再优化一下就出现了下面这种(不是很推荐,因为看起来很复杂)
这里只在需要的地方加synchronized,就不再锁整个方法,性能上提升了一丢丢(注意这里其实是双重判断的懒汉模式,还有一个只有一层判断,因为和一开始的那个一样存在线程安全问题这里不做展示)

public class LazySingleton {// 注意:volatile关键字是必须的,防止指令重排序private static volatile LazySingleton instance;// 私有化构造方法,防止外部实例化private LazySingleton() {}// 获取单例实例的方法public static LazySingleton getInstance() {// 在第一次调用时才创建实例if (instance == null) {synchronized (LazySingleton.class) {// 注意这里使用的是双重判断,防止多线程并发时重复创建实例// 如果不加下面这个判断,多线程并发时,可能会创建多个实例if (instance == null) {try {// 模拟处理业务逻辑耗时Thread.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}instance = new LazySingleton();}}}return instance;}public static void main(String[] args) {for (int i = 0; i < 100; i++) {new Thread(() -> {System.out.println(LazySingleton.getInstance().hashCode());}).start();}}
}

通过比较可以发现,饿汉模式是不管需不需要都会创建一个实例,有点浪费资源,然后在程序启动的时候会拖慢一点速度。懒汉模式虽然是在需要的时候才创建实例,但是因为使用了synchronized关键字,所以在使用的时候也会有性能问题。虽然问题都不大,但是有些完美主义可能就接受不了,所以下面我们再优化一下。

直接上代码(建议使用,目前来看应该是最完美的实现方式,唯一的缺点就是不能反序列化)
由于静态内部类只有在被使用的时候才会被加载,所以单例实例的创建会延迟到 getInstance() 方法被调用的时候。而且由于类加载过程是线程安全的,所以这种方式也是线程安全的。

public class Singleton {// 私有化构造方法,防止外部实例化private Singleton() {}// 静态内部类持有单例实例private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}// 获取单例实例的方法public static Singleton getInstance() {try {// 模拟处理业务逻辑耗时Thread.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}return SingletonHolder.INSTANCE;}public static void main(String[] args) {for (int i = 0; i < 100; i++) {new Thread(() -> {System.out.println(Singleton.getInstance().hashCode());}).start();}}
}

执行一下

在这里插入图片描述

那能不能写一个更完美的,让它能反序列化呢?答案当然是可以的!
直接上代码(虽然看起来很牛逼,用起来也很牛逼,但是不建议使用,违背Java代码设计原则)

public enum EnumSingleton {INSTANCE;// 注意枚举不是类没有构造方法// 这里可以用来处理业务逻辑public void doSomething() {System.out.println("do something");}public static void main(String[] args) {for (int i = 0; i < 100; i++) {new Thread(() -> {System.out.println(EnumSingleton.INSTANCE.hashCode());}).start();}}
}

执行一下

在这里插入图片描述

还有些其它的方式就不讲了,这几种基本就是最常见的,大家根据实际业务情况自行选择就好


http://www.mrgr.cn/p/33133543

相关文章

基于SpringBoot后端实现连接MySQL数据库并存贮数据

目录 一、什么是MySQL数据库 二、基于SpringBoot框架连接MySQL数据库 1、首先添加MySQL依赖&#xff1a; 2、配置数据库连接&#xff1a; 3、创建实体类&#xff1a; 4、创建Repository接口&#xff1a; 5、使用Repository&#xff1a; 三、编写业务SQL语句 1、使用Spring Data…

微服务高级篇(三):分布式缓存+Redis集群

文章目录 一、单点Redis的问题及解决方案二、Redis持久化2.1 单机安装Redis2.2 RDB持久化2.3 AOF持久化2.4 RDB和AOF对比 三、Redis主从3.1 搭建Redis主从架构3.1.1 集群结构3.1.2 准备实例和配置3.1.3 启动3.1.4 开启主从关系3.1.5 测试 3.2 数据同步3.2.1 全量同步【建立连接…

FreeType Glyph Conventions 翻译(4) ——Kerning 字间距调整

原文地址 https://freetype.org/freetype2/docs/glyphs/glyphs-4.html 目录字间距调整对子 Kerning pairs应用字间距调整 Applying kerning The term kerning refers to specific information used to adjust the relative positions of successive glyphs in a string of text…

MHA高可用+VIP漂移

目录一、环境搭建1、关闭防火墙firewalld,selinux2、每台主机安装MySQL二、基于GTID的主从复制1、修改/etc/my.cnf文件2、检查GTID状态3、配置主从复制4、从库设置三、部署MHA1、准备环境(所有节点)2、部署管理节点(可以部署在任何机器上)3、配置ssh信任4、启动测试(mana…

iOS客户端自动化UI自动化airtest+appium从0到1搭建macos+脚本设计demo演示+全网最全最详细保姆级有步骤有图

Android客户端自动化UI自动化airtest从0到1搭建macos脚本设计demo演示全网最全最详细保姆级有步骤有图-CSDN博客 避坑系列-必读&#xff1a; 不要安装iOS-Tagent &#xff0c;安装appium -这2个性质其实是差不多的都是为了安装wda。注意安装appium最新版本&#xff0c;安装完…

计算机复试面试问答准备(未完)

目录 1、理解多态性2、怎么逆置⼀个链表3、顺序表和链表的区别4、树的存储结构5、什么是哈夫曼树&#xff1f;简述哈夫曼树的构造过程。介绍哈夫曼树的特性。6、哈夫曼编码的编码和解码过程7、图的遍历方式8、图的存储方式9、最小生成树10、迪杰斯特拉算法11、佛洛依德算法12、…

逆向中常见的加密算法

逆向中常见的加密算法 1.Base64 1) 原理与特征: ​ a.原理:将3个byte(即38=24bit)切割为46,然后根据6bit表示的数字在base64表(64byte的表)寻找对应的值;如果待加密字符串长度不为3的整数,则在末尾处补0对齐,其中0对应的字符为=。 ​ b.特征:在反汇编代码中会出现0x…

Flutter逆向

环境配置(Blutter)及使用 参考 [原创]flutter逆向 ACTF native app-Android安全-看雪-安全社区|安全招聘|kanxue.com 其中在编译过程中遇到 -- Configuring done (2.5s) -- Generating done (0.0s) -- Build files have been written to: E:/blutter/build/blutter_dartvm3.4…

创新指南|如何将人工智能应用于未来的创新管理——并不断付诸实践

ChatGPT 的推出加剧了围绕人工智能的炒作&#xff0c;现在我们看到了前所未有的巨大进展。对于我们这些热衷于创新的人来说&#xff0c;这是一个激动人心的时刻。他们正在共同采取措施&#xff0c;充分利用人工智能进行创新管理。本文将阐述人工智能能为创新管理做什么&#xf…

文件包含一-WEB攻防-PHP应用文件包含LFIRFI伪协议编码算法无文件利用黑白盒

演示案例&#xff1a; 文件包含-原理&分类&利用&修复黑盒利用-VULWEB-有无包含文件白盒利用-CTFSHOW-伪协议玩法 #文件包含-原理&分类&利用&修复 1、原理 程序开发人员通常会把可重复使用的函数写到单个文件中&#xff0c;在使用某些函数时&#xff0c…

docker 的八大技术架构(图解)

docker 的八大技术架构 单机架构 概念&#xff1a; 应用服务和数据库服务公用一台服务器 出现背景&#xff1a; 出现在互联网早期&#xff0c;访问量比较小&#xff0c;单机足以满足需求 架构优缺点&#xff1a; 优点&#xff1a;部署简单&#xff0c;成本低 缺点&#xff1…

Java生成p12证书

本文章使用的环境 jdk1.8&#xff0c;spring-boot2.6.13 一、pom依赖 <dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk15on</artifactId><version>1.49</version></dependency><dependency>&…

PCL点云处理之最小中值平方(Lmeds法)拟合平面(二百三十四)

PCL点云处理之 最小中值平方法(Lmeds)拟合平面(二百三十四) 一、算法介绍一、拟合原理二、具体实现1.代码2.结果一、算法介绍 (本文提供详细注释,输出拟合平面参数和平面点云) Lmeds(Least Median of Squares)是一种统计学方法,用于拟合数据并减少异常值对拟合结果…

Spark-Scala语言实战(5)

在之前的文章中&#xff0c;我们学习了如何在scala中定义与使用集合和元组。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢。 Spark-Scala语言实战&#xff08;…

【Web应用技术基础】CSS(6)——使用 HTML/CSS 实现 Educoder 顶部导航栏

第一题&#xff1a;使用flex布局实现Educoder顶部导航栏容器布局 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Educoder</title><script src"https://cdn.staticfile.org/jquery/1.1…

不启动BMIDE,Teamcenter如何查看property的real property name

问题描述&#xff1a; Teamcenter客户端&#xff0c;查看Item 属性&#xff0c;属性名称默认显示的是Display Name。 在各类开发过程中&#xff0c;对属性的操作&#xff0c;需要使用real property name才能进行。开发可能不在server端&#xff0c;没有安装BMIDE&#xff0c;如…

机器学习——元学习

元学习&#xff08;Meta Learning&#xff09;是一种机器学习方法&#xff0c;旨在使模型能够学习如何学习。它涉及到在学习过程中自动化地学习和优化学习算法或模型的能力。元学习的目标是使模型能够从有限的训练样本中快速适应新任务或新环境。 在传统的机器学习中&#xff…

【漏洞复现】WordPress Plugin NotificationX 存在sql注入CVE-2024-1698

漏洞描述 WordPress和WordPress plugin都是WordPress基金会的产品。WordPress是一套使用PHP语言开发的博客平台。该平台支持在PHP和MySQL的服务器上架设个人博客网站。WordPress plugin是一个应用插件。 WordPress Plugin NotificationX 存在安全漏洞,该漏洞源于对用户提供的…

第三天-centos配置静态IP

跟着无涯老师学习安全知识的一天,今天遇到点问题。给新创建的centos配置静态IP的时候,可能是网卡配置文件里的内容修改或添加的不对,配置完之后网络不通。尝试了几次都没成功,明天再试试吧,今天就记这几个简单的命令,加油,晚安。

线程创建方式、构造方法和线程属性

欢迎各位&#xff01;&#xff01;&#xff01;推荐PC端观看 文章重点&#xff1a;学会五种线程的创造方式 目录 1.开启线程的五种方式 2.线程的构造方法 3.线程的属性及获取方法 1.开启线程的五种方式 创造线程的基本两步&#xff1a;&#xff08;1&#xff09;使用run方法…