【如何破坏单例模式(详解)】

✅如何破坏单例模式
- 💡典型解析
- ✅拓展知识仓
- ✅反射破坏单例
- ✅反序列化破坏单例
- ✅ObjectlnputStream
- ✅总结
- ✅如何避免单例被破坏
- ✅ 避免反射破坏单例
- ✅ 避免反序列化破坏单例
💡典型解析
单例模式主要是通过把一个类的构造方法私有化,来避免重复创建多个对象的。那么,想要破坏单例,只要想办注能够执行到这个私有的构造方法就行了。
✅拓展知识仓
一般来说做法有使用反射及使用反序列化都可以破坏单例。
我们先通过双重校验锁的方式创建一个单例,后文会通过反射及反序列化的方式尝试破坏这个单例。
package com.yangxiaoyuan;import java.io.Serializable;/*** Created by yangxiaoyuan on 23/12/24* 使用双重校验锁方式实现单例
*/public class Singleton implements Serializable {private volatile static Singleton singleton;private Singleton () {}public static Singleton getsingleton() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}
✅反射破坏单例
我们尝试通过反射技术,来破坏单例:
Singleton singleton1 = Singleton.getSingleton();//通过反射获取到构造函数
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
//将构造函数设置为可访问类型
constructor.setAccessible(true);
//调用构造函数的newInstance创建一个对象
Singleton singleton2 = constructor.newInstance();
//判断反射创建的对象和之前的对象是不是同一个对象
System.out.println(s1 == s2);
以上代码,输出结果为false,也就是说通过反射技术,我们给单例对象创建出来了一个 "兄弟” 。
setAccessible(true),使得反射对象在使用时应该取消Java 语言访检查,使得私有的构造函数能够被访问。
✅反序列化破坏单例
我们尝试通过序列化+反序列化来破坏一下单例:
package com.yangxiaoyuan;import java.io.*;public class SerializableDemo1 {//为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记//Exception直接抛出public static void main(String args) throws IOException, ClassNotFoundException {//Write Obj to fileObjectOutputStream oos = new ObjectOutputStream(new File0utputStream("tempFile"));oos.writeObject(Singleton.getsingleton());//Read Obi from fileFile file = new File("tempFile");ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));Singleton newInstance = (Singleton) ois.readObject();//判断是否是同一个对象System.out.println(newInstance == Singleton.getSingleton());}
}//false
输出结构为false,说明:
通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就破坏了Singleton的单例性。
这里,在介绍如何解决这个问题之前,我们先来深入分析一下,为什么会这样?在反序列化的过程中到底发生了什么。
✅ObjectlnputStream
对象的序列化过程通过ObjectOutputStream和ObiectlnputStream来实现的,那么带着刚刚的问题,分析一下ObjectlnputStream 的 readobject 方法执行情况到底是怎样的。
为了节省篇幅,这里给出ObiectlnputStream的 readobject 的调用栈:

这里看一下重点代码,readOrdinaryObject 万法的代码片段: code 3
private Object readOrdinaryObject(boolean unshared) throws IOException {//此处省略部分代码Object obj;try {obj = desc.isInstantiable() ? desc.newInstance() : null;} catch (Exception ex) {throw (IOException) new InvalidClassException(desc .forClass().getName(),
"unable to create instance").initCause(ex);}//此处省略部分代码if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {handles.setObject(passHandle, obj = rep);}}return obj;
}
code 3 中主要贴出两部分代码。先分析第一部分:
code3.1
Object obj;try {obj = desc.isInstantiable() ? desc.newInstance() : null;} catch (Exception ex) {throw (IOException) new InvalidClassException(desc .forClass().getName(),
"unable to create instance").initCause(ex);}
这里创建的这个obj对象,就是本方法要返回的对象,也可以暂时理解为是ObjectlnputStream的 readobject 返回的对象。

isInstantiable: 如果一个serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true。针对serializable和externalizable我会在其他文章中介绍。
desc.newInstance:该方法通过反射的方式新建一个对象。
然后看一下 newInstance 的源码:
public T newInstance(Object ... initargs) throws InstantiationException, IllegalAccessException,
IllegalArgumentException,InvocationTargetException {if (!override) {if (!Reflection.quickCheckMemberAccess(clazz,modifiers)) {Class<?> caller = Reflection.getCallerClass();checkAccess(caller, clazz, nul1, modifiers);}}if ((clazz.getModifiers() & Modifier.ENUM) != 0) {throw new IllegalArgumentException("Cannot reflectively create enum objects");}ConstructorAccessor ca = constructorAccessor; // read volatileif (ca == null) {ca = acquireConstructorAccessor();}@Suppresslarnings("unchecked")T inst = (T) ca.newInstance(initargs];return inst;
}
其中关键的就是 T inst = (T) ca.newInstance(initargs);这一步,这里实现的话在BootstrapConstructorAccessorlmpl中,实现如下:
public Object newInstance(Object[] args)
throws IllegalArgumentException,InvocationTargetException {try {return UnsafeFieldAccessorImpl.unsafe.allocateInstance(constructor.getDeclaringClass());} catch (InstantiationException e) {throw new InvocationTargetException(e);}
}
可以看到,这里通过Java 的 Unsafe 机制来创建对象的,而不是通过调用构造函数。这意味着即使类的构造函数是私有的,反序列化仍然可以创建该类的实例,因为它不依赖于常规的构造过程。
So,到目前为止,也就可以解释,为什么序列化可以破坏单例?
答:序列化会通过Unsafe直接分配的方式来创建一个新的对象。
✅总结
在涉及到序列化的场景时,要格外注意他对单例的破坏。
✅如何避免单例被破坏
✅ 避免反射破坏单例
反射是调用默认的构造函数创建出来的,只需要我们改造下构造函数,使其在反射调用的时候识别出来对象是不是被创建过就行了:
private Singleton() {if (singleton != null) {throw new RuntimeException("单例对象只能创建一次...");}
}
✅ 避免反序列化破坏单例
解决反序列化的破坏单例,只需要我们自定义反序列化的策略就行了,就是说我们不要让他走默认逻辑一直调用至Unsafe创建对象,而是我们干预他的这个过程,干预方式就是在Singleton类中定义 readResolve ,这样就可以解决该问题:
package com.yangxiaoyuan;import java.io.Serializable;// 使用双重校验锁方式实现单例public class Singleton implements Serializable {private volatile static Singleton singleton;private Singleton (){}public static Singleton getSingleton() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}private Object readResolve() {return singleton;}
}
还是运行以下测试类
package com.yangxiaoyuan;import java.io.*;public class SerializableDemo1 {//为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记//Exception直接抛出public static void main(Stringl] args) throws IOException, ClassNotFoundException {//Write Obj to fileObjectOutputStream oos = new ObiectOutputStream(new File0utputstream("tempFile")):oos.writeObject(Singleton.getSingleton());//Read Obj from fileFile file = new File("tempFile");ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));Singleton newInstance = (Singleton) ois.readObject();//判断是否是同一个对象System.out.println(newInstance == Singleton.getSingleton());}
}//true
本次输出结果为true。具体原理,我们回过头继续分析code 3中的第二段代码:
if (obj != null &&handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {handles .setObject(passHandle, obj = rep);}
}
hasReadResolveMethod :如果实现了serializable 或者 externalizable接口的类中包含 readResolve 则返回true。
invokeReadResolve :通过反射的方式调用要被反序列化的类的readResolve方法。
所以,原理也就清楚了,只要在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可可以防止单例被破坏。
相关文章:
【如何破坏单例模式(详解)】
✅如何破坏单例模式 💡典型解析✅拓展知识仓✅反射破坏单例✅反序列化破坏单例✅ObjectlnputStream ✅总结✅如何避免单例被破坏✅ 避免反射破坏单例✅ 避免反序列化破坏单例 💡典型解析 单例模式主要是通过把一个类的构造方法私有化,来避免重…...
什么是 SPI,它有什么用?
文章目录 什么是 SPI,它有什么用? 什么是 SPI,它有什么用? SPI 全称是 Service Provider Interface ,它是 JDK 内置的一种动态扩展点的实现。 简单来说,就是我们可以定义一个标准的接口,然后第三…...
FolkMQ 新的消息中间件,v1.0.25
简介 采用 “多路复用” “内存运行” “快照持久化” “Broker 集群模式”(可选)基于 Socket.D 网络应用协议 开发。全新设计,自主架构! 角色功能生产端发布消息(Qos0、Qos1)、发布定时消息ÿ…...
小程序入门-登录+首页
正常新建一个登录页面 创建首页和TatBar,实现登录后底部出现两个按钮 代码 "pages": ["pages/login/index","pages/index/index","pages/logs/logs" ],"tabBar": {"list": [{"pagePath"…...
React快速入门之组件
目录 组件JSX在标签使用{}嵌入JS表达式使用组件组件嵌套以🌲树的方式管理组件间的关系组件纯粹原则 组件 文件:Profile.js export default function Profile({isPacked true,head,stlyeTmp,src,size 80}) {if (isPacked) {head head &q…...
.NET Conf 2023 回顾 – 庆祝社区、创新和 .NET 8 的发布
作者: Jon Galloway - Principal Program Manager, .NET Community Team Mehul Harry - Product Marketing Manager, .NET, Azure Marketing 排版:Alan Wang .NET Conf 2023 是有史以来规模最大的 .NET 会议,来自全球各地的演讲者进行了 100 …...
Hadoop入门学习笔记——六、连接到Hive
视频课程地址:https://www.bilibili.com/video/BV1WY4y197g7 课程资料链接:https://pan.baidu.com/s/15KpnWeKpvExpKmOC8xjmtQ?pwd5ay8 Hadoop入门学习笔记(汇总) 目录 六、连接到Hive6.1. 使用Hive的Shell客户端6.2. 使用Beel…...
【K8S 基本概念】Kurbernetes的架构和核心概念
目录 一、Kurbernetes 1.1 简介 1.2、K8S的特性: 1.3、docker和K8S: 1.4、K8S的作用: 1.5、K8S的特性: 二、K8S集群架构与组件: 三、K8S的核心组件: 一、master组件: 1、kube-apiserve…...
WPS复选框里打对号,显示小太阳或粗黑圆圈的问题解决方法
问题描述 WPS是时下最流行的字处理软件之一,是目前唯一可以和微软office办公套件相抗衡的国产软件。然而,在使用WPS的过程中也会出现一些莫名其妙的错误,如利用WPS打开docx文件时,如果文件包含复选框,经常会出…...
对“企业数据资源相关会计处理暂行规定“的个人理解
附:2023年数据资源入表白皮书下载: 关注WX公众号: commindtech77, 获得数据资产相关白皮书下载地址 1. 回复关键字:数据资源入表白皮书 下载 《2023数据资源入表白皮书》 2. 回复关键字:光大银行 下载 光…...
JavaScript:函数隐含对象arguments/剩余参数. . .c/解构赋值
除了this,在函数内部还存在着一个隐含的参数arguments arguments 是一个类数组对象(伪数组) 调用函数时传递的所有实参,都被存储在arguments中 arguments[0] 表示的是第一个实参 arguments[1] 表示的是第二个实参 以此类推..…...
MFC窗体背景颜色的设置、控件白色背景问题、控件文本显示重叠问题、被父窗体背景覆盖的问题
文章目录 设置mfc窗体背景颜色窗体设置背景颜色后解决控件白色背景解决重复修改控件文本后重叠的问题自绘控件被父窗体背景覆盖的问题 设置mfc窗体背景颜色 设置窗体的背景颜色非常简单,只需要在窗体的OnEraseBkgnd里面填充窗体背景就可以了,甚至直接画…...
c++简易AI
今天小编一时雅兴大发,做了一个c的简易AI,还是很垃圾的! 题外话(每期都会有):我的蛋仔名叫酷影kuying,大家能加我好友吗? 上代码咯! #include<bits/stdc.h> #in…...
java获取两个List集合之间的交集、差集、并集
文章目录 方式一、jdk8 Stream求交集、并集、差集方式二、求交集方式三、collections4.CollectionUtils求交集、差集、并集 本文总结一下java中获取两个List之间的交集、补集、并集的几种方式。 最常用的通过for循环遍历两个集合的方式在这里就不整理了,主要整理一些…...
轻松实现iphone截图传电脑
目录 摘要 引言 用户登录工具和连接设备 生成截图 摘要 本篇博文介绍了克魔助手这款工具,解决了iPhone与Windows系统下图片传输的烦恼。通过连接同一Wi-Fi,使用克魔助手轻松实现了iPhone截图传输到电脑上的便捷操作。用户只需简单地下载并安装克魔助…...
【网络安全】upload靶场pass1-10思路
目录 Pass-1 Pass-2 Pass-3 Pass-4 Pass-5 Pass-6 Pass-7 Pass-8 Pass-9 Pass-10 🌈嗨!我是Filotimo__🌈。很高兴与大家相识,希望我的博客能对你有所帮助。 💡本文由Filotimo__✍️原创,首发于CSDN…...
共享单车之数据存储
文章目录 第1关:获取工作簿中的数据第2关:保存共享单车数据 第1关:获取工作簿中的数据 相关知识 获取工作簿中的信息,我们可以使用Java POI(POI是一个提供API给Java程序对Microsoft Office格式档案读和写的功能&#…...
Flink(十一)【状态管理】
Flink 状态管理 我们一直称 Flink 为运行在数据流上的有状态计算框架和处理引擎。在之前的章节中也已经多次提到了“状态”(state),不论是简单聚合、窗口聚合,还是处理函数的应用,都会有状态的身影出现。状态就如同事务…...
【三维目标检测/自动驾驶】IA-BEV:基于结构先验和自增强学习的实例感知三维目标检测(AAAI 2024)
系列文章目录 论文:Instance-aware Multi-Camera 3D Object Detection with Structural Priors Mining and Self-Boosting Learning 地址:https://arxiv.org/pdf/2312.08004.pdf 来源:复旦大学 英特尔Shanghai Key Lab /美团 文章目录 系列文…...
wefew
123212...
Arm Neoverse CMN-700缓存一致性互连网络架构解析
1. Arm Neoverse CMN-700架构概述Arm Neoverse CMN-700是Arm公司推出的新一代缓存一致性互连网络(Coherent Mesh Network)解决方案,专为高性能计算、云计算和基础设施应用设计。作为多核处理器系统中实现高效数据共享的关键基础设施ÿ…...
基于ESP32-S2与MAX17048的物联网电池监控系统设计与实现
1. 项目概述与核心价值 对于任何一个需要长期部署在户外的物联网设备,比如环境监测站、智能农业传感器或者远程摄像头,最让人头疼的问题往往不是代码bug,而是“它什么时候会没电?”。你不可能天天跑现场去检查,而设备…...
NeoPixel电源设计全攻略:从电流估算到多电源分配
1. 项目概述:为什么NeoPixel电源设计是成败关键如果你玩过NeoPixel或者类似的WS2812B可编程LED,大概率经历过这样的场景:精心设计的动画点亮了十几个灯珠,效果惊艳;但当你兴冲冲地把灯珠数量加到一百个,准备…...
学习信息系统项目管理师我们以什么视角学习?
如果你只是死记硬背那些定义,你会觉得这本书枯燥乏味,而且做题时很容易掉进陷阱。但如果你**“入戏”**,把自己当成那个掌握全局的项目经理,很多答案你凭直觉就能选对。为了帮你把“入戏”进行到底,我给你三个**“入戏…...
尼泊尔语语音合成落地难?ElevenLabs官方未公开的3个语言模型限制(附2024年Q2实测延迟/错误率/重音支持对比表)
更多请点击: https://intelliparadigm.com 第一章:尼泊尔语语音合成落地难?ElevenLabs官方未公开的3个语言模型限制(附2024年Q2实测延迟/错误率/重音支持对比表) 尼泊尔语(नेपाली)作为IS…...
2026年十大最佳地区搜索排名优化工具:权威榜单赋能企业高效增长
本文全面梳理了2026年十大主流地区搜索排名优化工具的核心功能与应用价值,旨在为本地企业提供客观、实用的选型参考。通过对各工具地域关键词布局、多平台同步能力及实时数据监控等关键模块的解析,结合具体参数指标与套餐定价,系统呈现不同场…...
Altium Designer20 从零到一:新手必备的安装与核心功能上手指南
1. Altium Designer20安装全攻略 第一次接触Altium Designer20(简称AD20)时,我和大多数电子设计新手一样,面对这个专业软件既兴奋又忐忑。记得当时为了完成课程设计,我在宿舍折腾了整整一个下午才搞定安装。现在回想起…...
嵌入式Tickless低功耗机制:从原理到FreeRTOS与裸机实践
1. 项目概述:从“忙等”到“休眠”,Tickless如何重塑嵌入式系统的能耗观在嵌入式开发领域,尤其是电池供电的设备上,功耗是悬在工程师头顶的达摩克利斯之剑。传统的实时操作系统(RTOS)或裸机调度,…...
Tessent OCC时钟控制器配置避坑指南:如何与现有时钟门控单元协同工作
Tessent OCC时钟控制器与现有门控单元协同设计实战指南 在28nm以下工艺节点的复杂SoC设计中,时钟域交叉(CDC)问题已成为影响测试覆盖率和良率提升的关键瓶颈。据统计,采用传统手动集成方法的项目平均需要花费23%的DFT工时用于解决…...
深入TMS320C6678中断控制器:从CIC、INTC到Event Combiner的底层机制图解
深入解析TMS320C6678中断控制器架构与实现机制 在嵌入式系统开发领域,中断处理机制的设计与实现往往是决定系统实时性和可靠性的关键因素。TMS320C6678作为一款高性能多核DSP处理器,其中断控制系统采用了分层式设计理念,通过片级中断控制器(C…...
