品读 Java 经典巨著《Effective Java》90条编程法则,第3条:用私有构造器或者枚举类型强化Singleton属性
《Effective Java》中的第3条编程法则主要是针对在开发过程如何实现单例模式,作者 Joshua Bloch 在书中给出了3种单例模式的实现方式:私有构造器和公有静态域、私有构造器和公有静态方法、枚举式。
什么是单例模式?
单例模式是一种设计模式,旨在确保一个类只有一个实例,其主要目的是控制类的实例化过程,避免创建多个对象实例;并提供一个全局访问点来获取该实例。这种模式适用于需要唯一对象来协调全局操作或资源管理的场景,例如配置管理器、线程池等。
单例实现方式
实现方式一:私有构造器和公有静态域(饿汉式单例模式)
通过私有构造器和公有静态域实现单例模式是一种简单且直接的方法,这种实现通常被称为饿汉式单例模式。
public class Singleton {// 单例对象,在类加载时创建public static final Singleton INSTANCE = new Singleton();// 私有构造器,防止外部实例化private Singleton() {// 可以添加初始化代码}// 其他方法public void doSomething() {// 实现具体的业务逻辑}
}
在这种实现中,通过将构造器设为 private,类外部无法直接创建实例,从而确保了单例模式的唯一性。而 static final 修饰符确保了在类加载阶段就创建并初始化实例,并且实例一旦创建后不可变,这样就进一步保证了单例实例的唯一性和一致性。整体设计既简单又有效。
实现方式二:私有构造器和公有静态方法(懒汉式单例模式 & 双重检查锁定)
私有构造器和公有静态方法的实现通常称为懒汉式单例模式:
public class Singleton {private static Singleton instance;private Singleton() {// 私有构造器}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
相比饿汉式单例模式实例的创建时机固定,懒汉式的实现将实例的初始化延迟到在第一次调用 getInstance 方法时创建,这样可以避免在程序启动时就初始化实例,从而节省资源。
但是在懒汉式的实现中,使用了synchronized 关键字保证在多线程环境下对 getInstance 方法的访问是线程安全的。然而,这种实现方式的缺点是每次调用 getInstance 方法时都要进行同步,这会带来性能开销。
幸运的是,由于我们使用静态工厂方法创建类的实例,那么我们就可以在方法种控制创建实例的时机,即在懒汉式单例模式的基础上进行优化,减少同步的开销来提高效率:
- 在获取实例时,首先检查实例是否已存在,如果已存在,则直接返回该实例。
- 如果实例不存在,再进行同步检查,以确保实例初始化的线程安全。
public class Singleton {// 使用 volatile 关键字确保线程安全private static volatile Singleton instance;// 私有构造器防止外部实例化private Singleton() {// 私有构造器}// 提供公共的静态方法获取实例public static Singleton getInstance() {if (instance == null) { // 第一次检查synchronized (Singleton.class) {if (instance == null) { // 第二次检查instance = new Singleton();}}}return instance;}
}
这种优化在获取实例时引入了两次检查,因此也被称为双重检查锁定:
- 第一次检查:在同步块外部进行,以减少不必要的同步开销。
- 第二次检查:在同步块内部进行,以确保实例的唯一性。
实现方式三:枚举式
由于枚举类型本身设计用于定义一组常量,因此实现单例模式时,通常一个枚举类型中只定义一个枚举实例:
public enum Singleton {INSTANCE;public void doSomething() {// 实现具体功能}
}
饿汉式 VS 懒汉式 VS 枚举式
饿汉式和懒汉式单例模式的实现,在反序列化和反射攻击可能会导致创建多个实例,破坏单例模式的唯一性。因此还需要添加以下处理:
-
实现
readResolve方法,确保在反序列化过程中,获取的对象仍然是单例实例,而不是新的实例:private Object readResolve() {return getInstance(); } -
在构造函数中增加检查,防止通过反射创建多个实例:
private Singleton() {if (instance != null) {throw new RuntimeException("Use getInstance() method to get the single instance of this class.");} }
枚举由于自身的特殊机制,使得枚举式相比前两者更适合单例模式得实现:
- 天然的线程安全:Java 枚举类型的实例在 JVM 加载时会创建一次且只创建一次,整个加载过程是线程安全的。因此,不需要额外的同步来保证线程安全。
- 防止反序列化攻击:枚举的实例由 JVM 管理,序列化和反序列化过程中保持唯一性。这意味着即使序列化和反序列化操作被恶意操控,也不会生成新的实例。
- 防止反射攻击:枚举类型的构造函数是私有的,当尝试使用反射创建枚举实例会抛出
IllegalArgumentException异常,从而保护了单例的唯一性。
这也是 Joshua Bloch 在书中提到单元素的枚举类型经常成为实现 Singleton 的最佳方法的原因。
相关文章:
品读 Java 经典巨著《Effective Java》90条编程法则,第3条:用私有构造器或者枚举类型强化Singleton属性
《Effective Java》中的第3条编程法则主要是针对在开发过程如何实现单例模式,作者 Joshua Bloch 在书中给出了3种单例模式的实现方式:私有构造器和公有静态域、私有构造器和公有静态方法、枚举式。 什么是单例模式? 单例模式是一种设计模式…...
如何在Flask中处理表单数据
在Flask中处理表单数据是一个常见的任务,它涉及从客户端接收数据并在服务器端进行解析和处理。Flask本身不直接提供表单验证的功能,但它可以与WTForms等库结合使用来简化表单处理过程。不过,即使没有WTForms,你仍然可以直接通过Fl…...
9月12日的学习
练习 #include "widget.h" #include "ui_widget.h" QListWidgetItem *p; Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget),socket(new QTcpSocket(this))//给客户端指针实例化空间及关联父组件 {ui->setupUi(this);//初始化,ui-…...
Java架构师未来篇大模型
目录 1. 大模型的定义2 大模型相关概念区分3 大模型的发展历程4. 大模型的特点5 大模型的分类6 大模型的泛化与微调7 大模型岗位需求8 理解大模型8.1 生活中的比喻8.2 大模型的定义9 大模型工作9.1 数据的积累9.2 模型的训练9.3 预测和应用10 大模型的实际应用10.1 语言处理10.…...
11.5.软件系统分析与设计-面向对象的程序设计与实现
面向对象的程序设计与实现 设计模式 Java代码 C代码...
中电金信:金融级数字底座“源启”:打造新型数字基础设施 筑牢千行百业数字化转型发展基石
近期,金融级数字底座“源启”登录中国电子《最轻大国重器》融媒体报道。从数字底座到数智底座,从金融行业到千行百业,“源启”用数智化转型的中国电子解决方案,为全球企业转型及安全发展提供强大动能。 立足中国电子科技创新成果&…...
IDEA怎么让控制台自动换行
IDEA怎么让控制台自动换行 操作流程 菜单>File>Settings>Editor>General>Console>勾选Use soft wraps in console 换行效果...
大模型笔记02--基于fastgpt和oneapi构建大模型应用平台
大模型笔记02--基于fastgpt和oneapi构建大模型应用平台 介绍部署&测试部署fastgptoneapi服务部署向量模型m3e和nomic-embed-text测试大模型 注意事项说明 介绍 随着大模型的快速发展,众多IT科技厂商都开发训练了各自的大模型,并提供了各具特色的AI产…...
linux-用户与权限管理-组管理
在 Linux 系统中,用户、组与权限管理是保障系统安全的重要机制。用户和组的管理不仅涉及对系统资源的访问控制,还用于权限的分配和共享。组管理在 Linux 中尤其重要,它能够帮助管理员组织用户并为不同的组分配特定权限,从而控制用…...
Day23_0.1基础学习MATLAB学习小技巧总结(23)——句柄图形
利用空闲时间把碎片化的MATLAB知识重新系统的学习一遍,为了在这个过程中加深印象,也为了能够有所足迹,我会把自己的学习总结发在专栏中,以便学习交流。 参考书目:《MATLAB基础教程 (第三版) (薛山)》 之前的章节都是…...
同步io和异步io
同步 I/O 和异步 I/O 是处理输入输出操作的两种不同策略,它们各有优缺点,适用于不同的场景。下面是它们的主要区别: 同步 I/O 定义:在同步 I/O 模型中,发起 I/O 操作的线程会被阻塞,直到操作完成。换句话说…...
AI基础 L19 Quantifying Uncertainty and Reasoning with Probabilities I 量化不确定性和概率推理
Acting Under Uncertainty 1 Reasoning Under Uncertainty • Real world problems contain uncertainties due to: — partial observability, — nondeterminism, or — adversaries. • Example of dental diagnosis using propositional logic T oothache ⇒ C av ity • H…...
C++ 关于时间的轮子
时间字符串转chrono::system_clock std::chrono::system_clock::time_point parse_date(const std::string& date_str) {std::tm tm {};std::istringstream ss(date_str);ss >> std::get_time(&tm, "%Y-%m-%d"); // 假设日期字符串格式为YYYY-MM-DDr…...
阿里达摩院:FunASR - onnxruntime 部署
阿里达摩院:FunASR - onnxruntime 部署 git clone https://github.com/alibaba/FunASR.git 切换到 onnxruntime cd FunASR/runtime/onnxruntime1下载 onnxruntime wget https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/dep_libs/onnxruntime-linux-x64-1.14.0.t…...
SpringMvc注解
SpringMvc注解 1 SpringMcv基础环境搭建 注:如果已经有SpringMvc项目直接跳过这个就可以了 1 新建项目 2.修改文件为packaging 为war包 <packaging>war</packaging> <?xml version"1.0" encoding"UTF-8"?> <pr…...
队列的基本概念及顺序实现
队列的基本概念 队列的定义 队列(Queue)简称队,也是一宗操作受限的线性表,只允许在表的一段进行插入,而在表的另一端进行删除。向队列中插入元素成为入队或进队;删除元素成为出队或离队。 特性:先进先出 (Fir…...
Leetcode 最长连续序列
算法流程: 哈希集合去重: 通过将数组中的所有元素放入 unordered_set,自动去除重复元素。集合的查找操作是 O(1),这为后续的快速查找提供了保证。 遍历数组: 遍历数组中的每一个元素。对于每个元素,首先检…...
linux网络编程——UDP编程
写在前边 本文是B站up主韦东山的4_8-3.UDP编程示例_哔哩哔哩_bilibili视频的笔记,其中有些部分博主也没有理解,希望各位辩证的看。 UDP协议简介 UDP 是一个简单的面向数据报的运输层协议,在网络中用于处理数据包,是一种无连接的…...
第四部分:1---文件内核对象,文件描述符,输出重定向
目录 struct file内核对象: 如何读写文件? 文件描述符在文件描述符表中的分配规则: 输出重定向初步解析: dup2实现复制文件描述符: struct file内核对象: struct file 是在内核空间中创建的用于描述文…...
如何在开发与生产环境中应用 Flask 进行数据库管理:以 SQLAlchemy 和 Flask-Migrate 为例
在使用 Flask 进行开发时,数据库管理是一个至关重要的环节。借助 SQLAlchemy 作为 ORM(对象关系映射)工具和 Flask-Migrate 进行数据库迁移,开发者可以高效地进行数据库管理,并在不同的环境(如开发环境和生…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...
yaml读取写入常见错误 (‘cannot represent an object‘, 117)
错误一:yaml.representer.RepresenterError: (‘cannot represent an object’, 117) 出现这个问题一直没找到原因,后面把yaml.safe_dump直接替换成yaml.dump,确实能保存,但出现乱码: 放弃yaml.dump,又切…...
AD学习(3)
1 PCB封装元素组成及简单的PCB封装创建 封装的组成部分: (1)PCB焊盘:表层的铜 ,top层的铜 (2)管脚序号:用来关联原理图中的管脚的序号,原理图的序号需要和PCB封装一一…...
goreplay
1.github地址 https://github.com/buger/goreplay 2.简单介绍 GoReplay 是一个开源的网络监控工具,可以记录用户的实时流量并将其用于镜像、负载测试、监控和详细分析。 3.出现背景 随着应用程序的增长,测试它所需的工作量也会呈指数级增长。GoRepl…...
