「全网最细 + 实战源码案例」设计模式——单例设计模式
核心思想:
-
属于创建型设计模式,核心目的是确保一个类在整个程序运行期间只有一个实例,并提供一个全局访问点来获取该实例。
-
控制共享资源的访问(如数据库链接、配置管理、日志处理器等)
-
真实世界类比:政府是单例模式的一个很好的示例。 一个国家只有一个官方政府。 不管组成政府的每个人的身份是什么,“某政府” 这一称谓总是鉴别那些掌权者的全局访问节点。
结构
所有单例的实现都包含以下两个相同的步骤:
-
将默认构造函数设为私有,防止其他对象使用单例类的
new运算符。 -
新建一个静态构建方法作为构造函数。该函数会“偷偷”调用私有构造函数来创建对象,并将其保存在一个静态成员变量中。此后所有对于该函数的调用都将返回这一缓存对象。
如果你的代码能够访问单例类,那它就能调用单例类的静态方法。无论何时调用该方法,它总是会返回相同的对象。
使用场景:
1. 需要唯一实例的场景:
-
配置管理类
-
日志记录器
-
数据库连接池
-
多线程环境中的任务调度器
2. 需要全局共享实例
-
以避免多个实例引发资源冲突或影响程序逻辑。
⭐实现方式:
1. 饿汉式(线程安全,类加载时初始化)
1.1. 静态变量式(常见方式)
// 饿汉式(静态变量)
public class Singleton {// 1. 私有化构造方法private Singleton() {}// 2. 创建一个静态变量,保存实例private static final Singleton instance = new Singleton();// 3. 提供一个公共的静态方法获取实例public static Singleton getInstance() {return instance;}
}
特点:
-
线程安全:类加载时实例化,JVM 保证线程安全。
-
缺点:类加载时即创建实例,即使未使用也会占用内存。
1.2. 静态代码块式
// 饿汉式(静态代码块)
public class Singleton {// 1. 私有化构造方法private Singleton(){}// 2. 创建一个静态对象private static Singleton instance;// 3. 在静态代码块中创建对象static {instance = new Singleton();}// 4. 提供获取对象的方法public static Singleton getInstance(){return instance;}
}
特点:
-
和静态变量方式类似,在类加载时实例化。
-
可以在静态代码块中加入额外逻辑,例如异常处理或配置初始化。
2. 懒汉式(线程不安全,延迟加载)
// 懒汉式,线程不安全
public class Singleton {// 1. 私有化构造方法private Singleton() {}// 2. 定义一个静态变量,用于存储唯一实例private static Singleton instance;// 3. 定义一个静态方法,用于获取唯一实例public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
优点:
-
实例在第一次使用时才初始化,节约资源。
缺点:
-
多线程情况下可能创建多个实例,线程不安全。
3. 线程安全的懒汉式
3.1. 同步方法
// 懒汉式,同步式,线程安全
public class Singleton {// 1. 私有化构造方法private Singleton() {}// 2. 定义一个静态变量,用于存储private static Singleton instance;// 3. 定义一个静态方法,用于获取唯一实例public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
缺点:
-
同步方法会导致性能下降,尤其是高并发访问时。
3.2. 双重检查锁(推荐)
// 懒汉式,双重检查锁方式
public class Singleton {// 1. 私有化构造方法private Singleton() {}// 2. 定义一个静态变量,用于存储实例,volatile保证可见性与有序性,避免指令重排private static volatile Singleton instance;// 3. 定义一个静态方法,用于获取唯一实例public static Singleton getInstance() {// 1.第一次判断,如果instance的值为null,则进入同步代码块if (instance == null) {// 2.同步代码块,保证线程安全synchronized (Singleton.class) {// 3.第二次判断,如果instance的值为null,则创建实例if (instance == null) {instance = new Singleton();}}}return instance;}
}
优点:
-
高效,只有在首次实例化时会加锁,之后不会。
注意:volatile 关键字防止指令重排,确保线程安全。
⭐为什么必须要加 volatile?
1、防止指令重排
在 Java 中,对象的实例化过程分为三步:
-
分配内存空间
-
初始化对象
-
将内存地址赋值给变量
由于指令重排的存在,步骤 2 和步骤 3 可能被调换执行。例如:
线程 A 在执行 instance = new Singleton() 时,可能执行了分配内存和赋值操作,但还未完成初始化。
此时,instance 已经不为 null,但它指向的对象尚未完全初始化。
如果线程 B 此时调用 getInstance(),判断 instance != null 为真,但实际访问的是一个未初始化完全的对象,这将导致程序出错。
加上 volatile 后,禁止指令重排序,确保初始化顺序正确。
2、保证变量的可见性
Java 的内存模型中,每个线程有自己的工作内存。一个线程对变量的修改,可能不会立即被其他线程所见。
加上 volatile 后,保证每次对 instance 的读操作都能获取到最新的值。
当线程 A 完成 instance 初始化后,其他线程(如 B 线程)立刻可见,而不会读取到旧值或中间状态。
3.3. ⭐静态内部类(推荐)
// 懒汉式,静态内部类方式
public class Singleton {// 1.构造函数私有化,外部不能newprivate Singleton() {}// 2.创建静态内部类private static class SingletonHolder {// 3.创建静态变量,保存实例private static final Singleton INSTANCE = new Singleton();}// 3.定义一个静态方法,用于获取唯一实例public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}
原理:
-
由于 JVM 加载外部类的过程中,不会加载静态内部类,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被
static修饰,保证只会被实例化一次,并且严格保证实例化顺序。
优点:
-
线程安全
-
实现了延迟加载,按需初始化
4. ⭐枚举单例(最安全,推荐)
// 枚举单例
public enum Singleton {INSTANCE;
}
优点:
-
简单
-
天然防止反射和序列化破坏单例
破坏单例
1. 序列化破坏单例
问题:序列化和反序列化可以通过 ObjectInputStream 创建一个新的实例,而不是返回现有的单例实例。
示例代码:
import java.io.*;public class Singleton implements Serializable {private static final long serialVersionUID = 1L;private static final Singleton INSTANCE = new Singleton();private Singleton() {}public static Singleton getInstance() {return INSTANCE;}public static void main(String[] args) throws Exception {Singleton instance1 = Singleton.getInstance();// 将对象序列化到文件ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.obj"));oos.writeObject(instance1);oos.close();// 从文件反序列化对象ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.obj"));Singleton instance2 = (Singleton) ois.readObject();// 验证是否为同一个实例System.out.println(instance1 == instance2); // 输出:false}
}
原因:
-
序列化机制会通过反序列化的过程创建一个新的对象实例,而不会调用单例类中的
getInstance()方法。
解决方案:实现 readResolve() 方法,确保反序列化时返回现有实例。
private Object readResolve() {return INSTANCE;
}
2. 反射破坏单例
问题:通过反射,能够直接调用私有构造方法,创建多个实例。
示例代码:
import java.lang.reflect.Constructor;public class Singleton {private static final Singleton INSTANCE = new Singleton();private Singleton() {}public static Singleton getInstance() {return INSTANCE;}public static void main(String[] args) throws Exception {Singleton instance1 = Singleton.getInstance();// 使用反射创建新实例Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();constructor.setAccessible(true);Singleton instance2 = constructor.newInstance();// 验证是否为同一个实例System.out.println(instance1 == instance2); // 输出:false}
}
原因:
-
反射可以访问私有构造方法并直接调用,从而绕过单例模式的限制。
解决方案:
1、在构造方法中防止重复实例化:
private static boolean isCreated = false;private Singleton() {if (isCreated) {throw new RuntimeException("Singleton instance already created!");}isCreated = true;
}
2、使用枚举单例:
枚举类的单例天然防止反射和序列化破坏。
public enum Singleton {INSTANCE;
}
3. 总结
-
序列化破坏:通过
readResolve()方法解决。 -
反射破坏:通过构造方法检查或使用枚举单例解决。
-
推荐方式:使用 枚举单例,最简单且最安全,能有效防止这两种破坏。
在源码中的应用
1. Runtime 类
-
简介:
Runtime类允许应用程序与运行时环境交互,比如调用垃圾回收、运行外部命令等。 -
实现方式:通过 饿汉式单例 实现。
源码分析:
public class Runtime {private static final Runtime currentRuntime = new Runtime(); // 饿汉式实例化private Runtime() {} // 私有化构造方法public static Runtime getRuntime() {return currentRuntime; // 返回唯一实例}public void gc() {// 调用垃圾回收}public void exit(int status) {// 退出 JVM}
}
特点:
-
全局唯一实例。
-
使用饿汉式,保证线程安全。
2. Desktop 类
-
简介:
Desktop类用来打开用户默认的应用程序(如浏览器、邮件客户端等)。 -
实现方式:通过 懒汉式单例 实现。
源码分析:
public final class Desktop {private static Desktop desktop;private Desktop() {}public static synchronized Desktop getDesktop() {if (desktop == null) {desktop = new Desktop(); // 懒汉式单例}return desktop;}public void browse(URI uri) {// 打开 URI}
}
特点:
-
使用同步方法保证线程安全。
-
懒加载,实例在需要时创建。
3. Logger 类( java.util.logging.Logger )
-
简介:
Logger是 Java 的日志工具类,用于记录和管理应用程序日志。 -
实现方式:内部使用单例模式管理全局日志管理器(
LogManager)。
源码分析(核心部分):
public class Logger {private static final LogManager manager = LogManager.getLogManager(); // 单例的 LogManagerprotected Logger(String name, String resourceBundleName) {// Logger 构造方法}public static Logger getLogger(String name) {return manager.getLogger(name); // 通过单例 LogManager 获取 Logger}
}
特点:
-
LogManager作为单例管理所有Logger实例。 -
getLogger方法确保每个名称对应的Logger是唯一的。
4. 总结
在 JDK 源码中,单例模式被广泛应用于需要 全局唯一实例 或 资源共享 的场景:
-
饿汉式:
Runtime类。 -
懒汉式:
Desktop类。 -
组合模式:
Logger类中的LogManager单例。
这些设计的核心目标是:确保全局状态的一致性、节省资源以及简化管理。
单例模式优缺点:
与其他模式的关系:
-
外观模式类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。
-
如果你能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例类似了。 但这两个模式有两个根本性的不同。
-
只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。
单例对象可以是可变的。 享元对象是不可变的。
-
抽象工厂模式、 生成器模式和原型模式都可以用单例来实现。
文章转载自:SlackClimb
原文链接:「全网最细 + 实战源码案例」设计模式——单例设计模式 - SlackClimb - 博客园
体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构
相关文章:
「全网最细 + 实战源码案例」设计模式——单例设计模式
核心思想: 属于创建型设计模式,核心目的是确保一个类在整个程序运行期间只有一个实例,并提供一个全局访问点来获取该实例。 控制共享资源的访问(如数据库链接、配置管理、日志处理器等) 真实世界类比:政府…...
第01章 19 通过点数据逐级构建球体体数据的综合性小例子
用VTK库来创建一个三维图像数据(vtkImageData),并填充标量数据以表示一个球体的体数据。球的半径为50,体数据的空间间隔为1.0/1000。 首先,我需要包含VTK的头文件,并且创建一个vtkImageData对象。然后&…...
CVE-2024-23897-Jenkins任意文件读取漏洞复现
content Jenkins是什么CVE-2024-23897总结修复建议 Jenkins是什么 Jenkins是一人基于Java开发的、可扩展的持续集成引擎,用于持续、自动地构建/测试软件项目,可以监控一些定时执行的任务。 官网文档: Jenkins是一款开源 CI&CD 软件&…...
前端react后端java实现提交antd form表单成功即导出压缩包
前端(React Ant Design) 1. 创建表单:使用<Form>组件来创建你的表单。 2. 处理表单提交:在onFinish回调中发起请求到后端API,并处理响应。 import React from react; import { Form, Input, Button } from ant…...
基于ESP32的桌面小屏幕实战[6]:环境搭建和软件基础
摘要 本文分为两部分:Linux开发环境搭建和软件基础。Linux开发环境搭建介绍了Ubuntu虚拟机安装及SSH、Samba配置,可以实现用VSCode操作虚拟机。为了后续工作,搭建了乐鑫ESP32 SDK环境。软件基础介绍了Linux开发常用的软件基础,包…...
接口(完)
大家好,今天我们着重来总结一下接口的知识,并且将接口和抽象类的区别罗列一下,帮助我们更好的认识抽象类和接口。 2.7 抽象类和接口的区别. 抽类和接口都是Java中多态的常见使用方式,都需要重点掌握,同时又要认清两者的区别(重要!!…...
数据结构——实验七·排序
欢迎各位大佬们来到Tubishu的博客🌟 Tubishu是一名计算机本科生,不定期发送一些在学校的成果供佬们消遣~希望能为佬的编程之路添砖加瓦⭐🔥 求各位大佬们垂怜🔥点赞评论一下呗🔥🔥 本文专栏 ➡️ 数据结构 …...
JVM堆空间
JVM(Java虚拟机)堆空间是Java内存管理的核心区域之一,用于存储Java对象实例。以下是关于JVM堆空间的详细介绍: 1. 堆空间的作用 • 存储对象实例:几乎所有的Java对象实例(通过new关键字创建的对象…...
【详细】SSH公私钥认证与渗透测试攻击场景
SSH(Secure Shell)是一个用于远程登录和执行命令的网络协议,其认证方式通常有两种:基于密码的认证和基于公私钥的认证。本文将详细介绍SSH公私钥认证机制,并探讨在渗透测试场景中,如何利用对靶机具有读取和…...
常见的多媒体框架(FFmpeg GStreamer DirectShow AVFoundation OpenMax)
1.FFmpeg FFmpeg是一个非常强大的开源多媒体处理框架,它提供了一系列用于处理音频、视频和多媒体流的工具和库。它也是最流行且应用最广泛的框架! 官方网址:https://ffmpeg.org/ FFmpeg 的主要特点和功能: 编解码器支持: FFmpe…...
C++异步future
🌎 C11异步futrue 文章目录: C11异步futrue future介绍 应用场景 future操作 std::async函数模版 std::packaged_task类模版 std::promise类模版 🚀future介绍 std::future是C11标准库…...
Oracle 12c 中的 CDB和PDB的启动和关闭
一、简介 Oracle 12c引入了多租户架构,允许一个容器数据库(Container Database, CDB)托管多个独立的可插拔数据库(Pluggable Database, PDB)。本文档旨在详细描述如何启动和关闭CDB及PDB。 二、容器数据库 (CDB) 2.1…...
Vue组件开发-使用 html2canvas 和 jspdf 库实现PDF文件导出 设置页面大小及方向
在 Vue 项目中实现导出 PDF 文件、调整文件页面大小和页面方向的功能,使用 html2canvas 将 HTML 内容转换为图片,再使用 jspdf 把图片添加到 PDF 文件中。以下是详细的实现步骤和代码示例: 步骤 1:安装依赖 首先,在项…...
chrome插件:网页图片高清下载
前置条件: 安装有chrome谷歌浏览器的电脑 使用步骤: 1.打开chrome扩展插件 2.点击管理扩展程序 3.加载已解压的扩展程序 4.选择对应文件夹 5.成功后会出现一个扩展小程序 6.点击对应小程序 7.输入需要访问的网址,点击扩展插件即可进行图片…...
汽车定速巡航
配备定速巡航功能的车型,一般在方向盘附近设有4~6个按键(可能共用键位)。 要设置定速巡航,不仅需要方向盘上的按键,还要油门配合。 设置的一般流程: 开关:类似步枪上的“保险”,按…...
CNN-BiLSTM卷积双向长短期记忆神经网络时间序列预测(Matlab完整源码和数据)
CNN-BiLSTM卷积双向长短期记忆神经网络时间序列预测(Matlab完整源码和数据) 目录 CNN-BiLSTM卷积双向长短期记忆神经网络时间序列预测(Matlab完整源码和数据)预测效果基本介绍 CNN-BiLSTM卷积双向长短期记忆神经网络时间序列预测一…...
WPF基础 | WPF 布局系统深度剖析:从 Grid 到 StackPanel
WPF基础 | WPF 布局系统深度剖析:从 Grid 到 StackPanel 一、前言二、Grid 布局:万能的布局王者2.1 Grid 布局基础:构建网格世界2.2 子元素定位与跨行列:布局的精细操控2.3 自适应布局:灵活应变的秘诀 三、StackPanel…...
14-6-2C++STL的list
(一)list对象的带参数构造 1.list(elem);//构造函数将n个elem拷贝给本身 #include <iostream> #include <list> using namespace std; int main() { list<int> lst(3,7); list<int>::iterator it; for(itlst.begi…...
【AI论文】Sigma:对查询、键和值进行差分缩放,以实现高效语言模型
摘要:我们推出了Sigma,这是一个专为系统领域设计的高效大型语言模型,其独特之处在于采用了包括DiffQKV注意力机制在内的新型架构,并在我们精心收集的系统领域数据上进行了预训练。DiffQKV注意力机制通过根据查询(Q&…...
私域流量池构建与转化策略:以开源链动2+1模式AI智能名片S2B2C商城小程序为例
摘要:随着互联网技术的快速发展,流量已成为企业竞争的关键资源。私域流量池,作为提升用户转化率和增强用户粘性的有效手段,正逐渐受到企业的广泛关注。本文旨在深入探讨私域流量池构建的目的、优势及其在实际应用中的策略…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...
golang循环变量捕获问题
在 Go 语言中,当在循环中启动协程(goroutine)时,如果在协程闭包中直接引用循环变量,可能会遇到一个常见的陷阱 - 循环变量捕获问题。让我详细解释一下: 问题背景 看这个代码片段: fo…...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
