Java设计模式之单例模式(多种实现方式)
虽然写了很多年代码,但是说真的对设计模式不是很熟练,虽然平时也会用到一些,但是都没有深入研究过,所以趁现在有空练下手
这章主要讲单例模式,也是最简单的一种模式,但是因为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();}}
}
执行一下
还有些其它的方式就不讲了,这几种基本就是最常见的,大家根据实际业务情况自行选择就好
相关文章:

Java设计模式之单例模式(多种实现方式)
虽然写了很多年代码,但是说真的对设计模式不是很熟练,虽然平时也会用到一些,但是都没有深入研究过,所以趁现在有空练下手 这章主要讲单例模式,也是最简单的一种模式,但是因为spring中bean的广泛应用&#…...
Miracast投屏探索
Miracast是一种Wi-Fi Alliance推出的无线显示技术,允许在支持Miracast标准的设备之间进行屏幕镜像和内容共享。在Miracast技术中,通常会涉及到两种角色:Source(发送端)和Sink(接收端)。 Source&…...

2024年幻兽帕鲁服务器优惠价格表手动整理,最全报价
2024年全网最全的幻兽帕鲁服务器租用价格表,阿里云幻兽帕鲁游戏服务器26元1个月、腾讯云32元一个月、京东云26元一个月、华为云24元1个月,阿腾云atengyun.com整理最新幻兽帕鲁专用4核16G、8核16G、8核32G游戏服务器租用价格表大全: 阿里云幻…...
使用Python自动备份重要文件:一步一步的教程
目录 1 重要性说明1.1 数据丢失的风险1.2 自动化备份的好处1.3 提高数据安全性和恢复力 2 工具和技术简介2.1 os库2.2 shutil库2.3 glob库2.4 pathlib库 3 备份策略设计3.1 全备份3.2 增量备份3.3 差异备份3.4 根据需求选择备份策略 4 编写备份脚本4.1 步骤拆解步骤 1: 选择源文…...
python学习
Python面试题大全 - 50道经典面试题 - 掘金 yoloV5:yolov5: YOLOv5 汉化版,保持官方同步更新 yoloV8使用案例:用YOLOv8一站式解决图像分类、检测、分割…… - 掘金 yoloV8使用案例douyin:https://www.douyin.com/user/self?modal_id7276287878194285…...

【使用redisson完成延迟队列的功能】使用redisson配合线程池完成异步执行功能,延迟队列和不需要延迟的队列
1. 使用redisson完成延迟队列的功能 引入依赖 spring-boot-starter-actuator是Spring Boot提供的一个用于监控和管理应用程序的模块 用于查看应用程序的健康状况、审计信息、指标和其他有用的信息。这些端点可以帮助你监控应用程序的运行状态、性能指标和健康状况。 已经有了…...
Linux 性能分析工具 perf 的使用指南
什么是perf,可以用来干什么 perf 是 Linux 内核的性能分析工具集,它可以用来监控和分析系统和应用程序的性能。perf 提供了一系列功能强大的子命令,可以帮助开发者和系统管理员: 监控 CPU 使用率:识别最消耗 CPU 的代…...

【QT入门】 Qt代码创建布局之水平布局、竖直布局详解
往期回顾: 【QT入门】 Qt实现自定义信号-CSDN博客 【QT入门】 Qt自定义信号后跨线程发送信号-CSDN博客 【QT入门】 Qt内存管理机制详解-CSDN博客 【QT入门】 Qt代码创建布局之水平布局、竖直布局详解 先看两个问题: 1、ui设计器设计界面很方便…...
C 传递数组给函数
如果您想要在函数中传递一个一维数组作为参数,您必须以下面三种方式来声明函数形式参数,这三种声明方式的结果是一样的,因为每种方式都会告诉编译器将要接收一个整型指针。同样地,您也可以传递一个多维数组作为形式参数。 方式 1…...

二次开发Flink-coGroup算子支持迟到数据通过测输出流提取
目录 1.背景 2.coGroup算子源码分析 2.1完整的coGroup算子调用流程 2.2coGroup方法入口 2.3 CoGroupedStreams对象分析 2.4WithWindow内部类分析 2.5CoGroupWindowFunction函数分析 3.修改源码支持获取迟到数据测输出流 3.1复制CoGroupedStreams 3.2新增WithWindow.si…...

【容器源码篇】Set容器(HashSet,LinkedHashSet,TreeSet的特点)
文章目录 ⭐容器继承关系🌹Set容器🗒️HashSet源码解析构造方法public HashSet()public HashSet(Collection<? extends E> c)public HashSet(int initialCapacity, float loadFactor)HashSet(int initialCapacity, float loadFactor, boolean dum…...

网络工程师实验命令(华为数通HCIA)
VRP系统的基本操作 dis version #查看设备版本信息 sys #进入系统视图 system-name R1 #改设备名字为R1进入接口配置IP地址 int g0/0/0 ip address 192.168.1.1 255.255.255.0 #配置接口地址为192.168.1.1/255.255.255.0 ip address 192.168.1.2 24 sub #此…...
AI大模型学习:AI大模型在特定领域的应用
1. 引言 随着人工智能技术的飞速发展,AI大模型已成为推动科技创新的重要力量。从自然语言处理到图像识别,再到复杂决策支持系统,AI大模型在多个领域展现出了前所未有的潜力和应用广度。本文旨在深入探讨AI大模型在特定领域中的应用࿰…...
Channel 结合 Select 使用
在Go语言中,channel和select结合使用是一种强大的并发模式。channel允许在不同的goroutine之间安全地传递消息,而select使得goroutine可以同时等待多个通信操作(channel操作)。 select语句等待多个channel操作中的任意一个完成。…...

LeetCode-1669题:合并两个链表(原创)
【题目描述】 给你两个链表 list1 和 list2 ,它们包含的元素分别为 n 个和 m 个。请你将 list1 中下标从 a 到 b 的全部节点都删除,并将list2 接在被删除节点的位置。下图中蓝色边和节点展示了操作后的结果: 请你返回结果链表的头指针。 【…...

微服务高级篇(三):分布式缓存+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 全量同步【建立连接…...

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

day56 动态规划part13
300. 最长递增子序列 中等 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,…...

Mybatis别名 动态sql语句 分页查询
给Mybatis的实体类起别名 给Mybatis的xml文件注册mapper映射文件 动态sql语句 1 if 2 choose 3 where 4 foreach 一)if 查询指定名称商品信息 语法: SELECT * FROM goods where 11 <if test "gName!null"> and g.g_name like co…...

【算法题】三道题理解算法思想--滑动窗口篇
滑动窗口 本篇文章中会带大家从零基础到学会利用滑动窗口的思想解决算法题,我从力扣上筛选了三道题,难度由浅到深,会附上题目链接以及算法原理和解题代码,希望大家能坚持看完,绝对能有收获,大家有更好的思…...

Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
python爬虫——气象数据爬取
一、导入库与全局配置 python 运行 import json import datetime import time import requests from sqlalchemy import create_engine import csv import pandas as pd作用: 引入数据解析、网络请求、时间处理、数据库操作等所需库。requests:发送 …...

【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制
目录 节点的功能承载层(GATT/Adv)局限性: 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能,如 Configuration …...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...