当前位置: 首页 > news >正文

如何构造一个安全的单例?

为什么要问这个问题?

我们知道,单例是一种很常用的设计模式,主要作用就是节省系统资源,让对象在服务器中只有一份。但是实际开发中可能有很多人压根没有写过单例这种模式,只是看过或者为了面试去写写demo熟悉一下。那为啥说是一种常用的模式?
其实我们用的spring管理对象生命周期,用到默认的scope就是单例。这样的场景几乎每天都在用,所以我们不需要自己手写单例了。
那么为了面试,进大厂,是不是就要刷刷文章学习学习呢?当我们刷完单例的整体结构时,会发现还是很简单的嘛,无非就是懒汉、饿汉。饿汉上来就创建,没什么难的,懒汉可能会在创建的时候线程不安全,还要防止jvm在server模式下进行指令重排,加双层锁判断就ok啦!面试很简单嘛,照着文章中的背下来就行了。
但是,你有没有遇到面试官问你,如何构造一个安全的单例?注意,是安全的。
如果你没遇到,恭喜你,面试官不想为难你,或者他没把单例玩明白。如果你遇到了,很不幸,这个面试官是个注重细节的人,而且在给你挖坑。
当然,我觉得在面试的时候这么问单例的人,可能只有我。
刚刚说了,线程不安全加锁就解决了。但是,这里安全,可不只是是线程安全,光知道线程安全没什么特殊的,无非你准备过面试。那这里还有什么猫腻?
答案:

  1. 反射攻击,导致单例变成多例,不安全了
  2. 序列化、反序列化变成多例,不安全了

这两点,下面再说具体为什么和怎么解决。先说说,作为面试官的我为什么这么问?
首先,我知道现在存在很多刷题网站,说用过的人,并不一定真的用过,只是刷了题,我要筛出真正会的人,而不是刷过题的人。
其次,一个单例,考察的不是一个模式这么简单,如果回答出这两个答案的人,我会认为,他的java基础非常好,而且考虑问题非常全面和谨慎。怎么看出来的?
基础好:我们最常用的序列化方式恐怕就是json、xml、pb、hessian等协议,很少有人用java自带的字节流序列化。用字节流序列化只有一种情况,redis存储、消息报文投递、IO编程时考虑性能,还有可能对字节进行压缩。这个时候,如果你只是对Serializable接口有所了解,知道serialVersionUID就有一些浅了。如果你知道readResolve,那证明你对java序列化理解的很透彻。
考虑问题全面和谨慎:一般的人实现单例,只是满足功能就可以了,甚至不考虑懒汉的线程安全问题。如果你考虑反射攻击带来的危害,那你在做架构方案设计时,一定是很全面和谨慎的,你的方案也是可靠的。

反射攻击是什么?

如果不使用饿汉,不使用枚举做单例,那我们要么做静态内部类,要么做双重锁+volatile来保证线程安全。同时无论是饿汉还是懒汉,只要不用枚举,我们都需要做私有构造函数。如下:

//静态内部类实现方式
private Singleton(){} //不安全的点
public Singleton getInstance(){return Instance.INSTANCE;
}
private static class Instance{private static final Singleton INSTANCE = new Singleton();
}
//双重锁实现方式
private static volatile Singleton instance = null;
private Singleton(){} //不安全的点
public Singleton getInstance(){if(instance == null){synchronized(Singleton.class){if(instance == null){instance = new Singleton();}}}
}
//饿汉实现方式
private static final Singleton INSTANCE = new Singleton();
private Singleton(){} //不安全的点
public Singleton getInstance(){return INSTANCE;
}

上面的三种实现方式,注意私有构造函数(这里加了注释)是不安全的,本意是防止被调用者直接new Singleton()创建对象设置为私有,在一般情况下,这是没问题的。
但是用心良苦的人,可能会这么调用你:

for(int i=0;i<10000000;i++){Singleton.class.newInstance();//用反射绕过私有构造函数,直接创建对象
}

这个时候,你的业务是不是会Denial of service
所以这很坑,但是你会说,我写的单例代码在java服务器内部,怎么会被人这么调用?这是不可能发生的!没错,这没问题。如果你的代码是开源的,你怎么知道那些内心黑暗的人会不会从某个http接口伪造什么东西来触发newInstance()?仔细想想,这两年有多少人被FastJSON坑的大晚上不能安心睡觉,要紧急升级代码?恐怕开发阿里爸爸FastJSON团队,也不想出现这样的状况。
所以我们要严谨!

private Singleton(){throw new IllegalStateException();
}

当然,这个时候是无法使用双重锁+volatile方式创建单例的,因为自身调用也会抛异常。所以直接用静态内部类方式解决问题。

//静态内部类实现方式
private Singleton(){if(Instance.INSTANCE!=null){throw new IllegalStateException();//这下安全了}
} 
public Singleton getInstance(){return Instance.INSTANCE;
}
private static class Instance{private static final Singleton INSTANCE = new Singleton();
}

在这里插入图片描述

序列化反序列化会怎样?

直接上代码

public class Singleton implements Serializable{private Singleton() {if (Instance.INSTANCE != null) {//这里我可是防止了反射攻击哦!throw new IllegalStateException();}}public static Singleton getInstance() {return Instance.INSTANCE;}private static class Instance {private static final Singleton INSTANCE = new Singleton();}public static void main(String[] args) throws IOException, ClassNotFoundException {File file = new File("~/Desktop/Singleton.bin");Singleton singleton = Singleton.getInstance();ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));oos.writeObject(singleton);ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));Singleton singleton1 = (Singleton) ois.readObject();System.out.println(singleton == singleton1);}
}

在这里插入图片描述
这时候,是不是又会Denial of service
为啥会这样?其实很简单,只要实现Serializable接口的类对象,ObjectOutputStream会毫不犹豫的吐成字节,或者读回来,它才不管你是不是单例,当然它也不知道内存中有这么一个单例,直接在内存中创建对象了。
如何解决这个问题?

public class Singleton implements Serializable{private Singleton() {if (Instance.INSTANCE != null) {throw new IllegalStateException();}}public static Singleton getInstance() {return Instance.INSTANCE;}//实现readResolve接口就ok了private Object readResolve() throws ObjectStreamException {return Instance.INSTANCE;}private static class Instance {private static final Singleton INSTANCE = new Singleton();}public static void main(String[] args) throws IOException, ClassNotFoundException {File file = new File("/Users/baodi/Desktop/Singleton.bin");Singleton singleton = Singleton.getInstance();ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));oos.writeObject(singleton);ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));Singleton singleton1 = (Singleton) ois.readObject();System.out.println("源对象singleton == 反序列化对象 singleton1吗?"+(singleton == singleton1));}

实现readResolve,返回静态内部类的对象就可以了。
看看源码(我用的jdk1.8,1.6、1.7也一样)ObjectOutputStream.readObject()中会调用内部私有方法readObject0(),其中byte tc是把对象头读出来
在这里插入图片描述
通过switch case(tc)判断对象的类型,这里我们用的是OBJECT类型,魔数为0x73
在这里插入图片描述
在这里插入图片描述
这时候,会调用内部私有的readOrdinaryObject()方法
在这里插入图片描述
这里就是调用我们重写的readResolve()方法啦!
在这里插入图片描述
这就是jdk的大佬们为提供的一个hook方法,我们可以用它保证序列化和反序列化的安全。
有人一定会说,你有病,序列化一个单例!不,我没病,只是你没用到过而已
有人也会说,用枚举不就屏蔽这些问题了吗?不,如果JDK1.6的情况下是不能把枚举当做单例对象玩的。

好了,到这里就结束了吗?
不,记得给你的单例类加上final,防止被继承后重写!

结论

  1. 面试不只是刷刷题就ok了
  2. 请认真的对待你写过的每一个代码,因为你很可能把别人坑了,比如FastJSON
  3. 做技术要刨根问底,网上的资料都是说如何序列化不安全的问题,并没有给出ObjectOutputStream.readObject()执行原理分析,不信你搜
  4. 严谨、夯实基础

相关文章:

如何构造一个安全的单例?

为什么要问这个问题&#xff1f; 我们知道&#xff0c;单例是一种很常用的设计模式&#xff0c;主要作用就是节省系统资源&#xff0c;让对象在服务器中只有一份。但是实际开发中可能有很多人压根没有写过单例这种模式&#xff0c;只是看过或者为了面试去写写demo熟悉一下。那…...

单片机开发 esp8266

一、固件界面 二、项目介绍 固件名称&#xff1a;esp8266-universalboard v1.0 提供商&#xff1a; 半条虫(466814195) 下载&#xff1a;esp8266-universalboard.bin 源码地址&#xff1a;Gitlab...

Linux 查看版本和用户权限提升实践心得

文章目录 linux (Ubuntu内核)查看版本版本信息解释内置yum工具&#xff1f;用户权限提升操作步骤 查看deepin系统的版本和其debian的版本遇到的问题&#xff1a;deepin-release文件不存在 linux (Ubuntu内核)查看版本 使用lsb_release命令&#xff1a; lsb_release -a该命令将…...

多线程编程5:线程同步和进程通信(C++11和linux)

常见的线程同步 linux&#xff1a; 互斥锁&#xff1a;实现共享资源的串行访问&#xff0c;有三个版本普通锁(默认属性)&#xff0c;检错锁(可以防止相同线程重复加锁)和递归锁(相同线程可以重复加锁)条件变量&#xff1a;配合互斥锁使用&#xff0c;实现线程之间的通信&#…...

tensorrt官方int8量化方法汇总

原理及操作 量化的基本原理及流程可参看懂你的神经网络量化教程&#xff1a;第一讲、量化番外篇、TensorRT中的INT8、tensorRT int8量化示例代码 Tensorrt 方式1&#xff1a;trtexec&#xff08;PTQ的一种&#xff09; int8量化 trtexec --onnxXX.onnx --saveEnginemodel.…...

21、p6spy输出执行SQL日志

文章目录 1、背景2、简介3、接入3.1、 引入依赖3.2、修改database参数&#xff1a;3.3、 创建P6SpyLogger类&#xff0c;自定义日志格式3.4、添加spy.properties3.5、 输出样例 4、补充4.1、参数说明 1、背景 在开发的过程中&#xff0c;总希望方法执行完了可以看到完整是sql语…...

实力认证!TDengine 入选 Gartner 中国数据分析与人工智能技术成熟度曲线

近日&#xff0c;国际权威研究机构 Gartner 发布了《2023 年中国数据分析及人工智能技术成熟度曲线》&#xff08;即《Hype Cycle for Data, Analytics and AI in China, 2023》&#xff09;报告&#xff0c;TDengine 成功入选实时数据管理领域代表产品。 作为评估全球新技术成…...

如何将jar包部署到宝塔

尝试多种方式上传&#xff0c;但启动一直失败&#xff0c;这种方式亲测是好使的 项目内修改位置 在pom.xml文件中将mysql的scope改成provided&#xff0c;如果是固定的版本号会出现问题 之后就可以打包啦&#xff0c;直接点击maven中的package 找到打包文件的位置&#xff…...

el-tree-select那些事

下拉菜单树形选择器 用于记录工作及日常学习涉及到的一些需求和问题 vue3 el-tree-select那些事 1、获取el-tree-select选中的任意层级的节点对象 1、获取el-tree-select选中的任意层级的节点对象 1-1数据集 1-2画面 1-3代码 1-3-1画面代码 <el-tree-selectv-model"s…...

分布式任务调度框架之开山鼻祖:Quartz

1.简介 最近我司上线使用了分布式任务调度框架&#xff1a;XXL-JOB&#xff0c;方便对任务的管理控制。本来一开始就想讲述一下该框架&#xff0c;但是在学习了解过程中发现该框架式基于Quartz思想开发实现的&#xff0c;Quartz 是一个很火的开源任务调度框架&#xff0c;完全…...

Django实现音乐网站 ⑺

使用Python Django框架制作一个音乐网站&#xff0c; 本篇主要是后台对歌手原有实现功能的基础上进行优化处理。 目录 新增编辑 表字段名称修改 隐藏单曲、专辑数 姓名首字母 安装xpinyin 获取姓名首字母 重写保存方法 列表显示 图片显示处理 引入函数 路径改为显示…...

PO、DTO和VO的区别

PO&#xff1a;Persistence Object&#xff0c;是持久化对象&#xff0c;也叫实体对象&#xff08;Entity&#xff09;&#xff0c;是对象到关系表的映射。在关系型数据库中&#xff0c;每张表对应着一个PO&#xff0c;PO 中的属性值与表中数据一一对应&#xff0c;通过数据访问…...

ReadableStream流的js处理

需求 今天接了一个gpt的项目。需要对接接口。后端返回的数据是ReadableStream类型的。记录下如何处理ReadableStream类型的数据 代码 const response ... // response 是服务端返回的数据// 创建了一个数据读取器 const reader response.getReader(); // 创建了一个文本解…...

图扑软件入选 2023 中国信通院“铸基计划”全景图

7 月 27 日&#xff0c;由中国信通院主办的“2023 数字生态发展大会”暨中国信通院“铸基计划”年中会议在北京召开。本次大会重磅发布了《高质量数字化转型产品及服务全景图&#xff08;2023 上半年度&#xff09;》。图扑软件凭借自研 HT for Web 数字孪生可视化产品成功入选…...

Go Fyne:一种现代的,跨平台的 GUI 开发工具集

Fyne 是一个用 Go 语言写的&#xff0c;提供丰富&#xff0c;现代化的用户界面&#xff08;UI&#xff09;的开发工具集。Fyne 提供了一种简单而直观的方式来构建应用程序&#xff0c;并且注意到它是跨平台的&#xff0c;可以在 Windows&#xff0c;macOS&#xff0c;Linux&…...

React Dva 操作models中的subscriptions讲述监听

接下来 我们来看一个models的属性 之前没有讲到的subscriptions 我们可以在自己有引入的任意一个models文件中这样写 subscriptions: {setup({ dispatch, history }) {console.log(dispatch);}, },这样 一进来 这个位置就会触发 这里 我们可以写多个 subscriptions: {setup…...

标准化归一化 batch norm, layer norm, group norm, instance norm

Layer Normalization - EXPLAINED (in Transformer Neural Networks) Layer Normalization - EXPLAINED (in Transformer Neural Networks) 0~4min:什么是multi-head attention 5~7min:layer norm图示 7~9min:公式举例layer norm 9:54-end:layer norm的代码示例 group n…...

Zabbix监控系统详解及配置

前言 作为一个运维&#xff0c;需要会使用监控系统查看服务器状态以及网站流量指标&#xff0c;利用监控系统的数据去了解上线发布的结果&#xff0c;和网站的健康状态。利用一个优秀的监控软件&#xff0c;我们可以&#xff1a; 通过一个友好的界面进行浏览整个网站所有的服务…...

【100天精通python】Day29:文件与IO操作_XML文件处理

目录 专栏导读 一、XML文件概述 1. 标签和元素 2. 嵌套结构 3. 属性 4. 命名空间 5. CDATA节 6. 注释 7. 验证与验证语言 8. 扩展性 二、XML文件处理常见操作 1. 解析XML文件 2. 创建和编辑XML文件 3. 修改XML文件 4. 查询XML元素 5 遍历XML元素 6. 删除XML元…...

人工智能的未来:探索下一代生成模型

推荐&#xff1a;使用 NSDT场景编辑器 助你快速搭建可编辑的3D应用场景 生成式 AI 目前能够做什么&#xff0c;以及探索下一波生成式 AI 模型需要克服的当前挑战&#xff1f; 如果你跟上科技世界的步伐&#xff0c;你就会知道生成式人工智能是最热门的话题。我们听到了很多关于…...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

基于FPGA的PID算法学习———实现PID比例控制算法

基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容&#xff1a;参考网站&#xff1a; PID算法控制 PID即&#xff1a;Proportional&#xff08;比例&#xff09;、Integral&#xff08;积分&…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

五年级数学知识边界总结思考-下册

目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解&#xff1a;由来、作用与意义**一、知识点核心内容****二、知识点的由来&#xff1a;从生活实践到数学抽象****三、知识的作用&#xff1a;解决实际问题的工具****四、学习的意义&#xff1a;培养核心素养…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放

简介 前面两期文章我们介绍了I2S的读取和写入&#xff0c;一个是通过INMP441麦克风模块采集音频&#xff0c;一个是通过PCM5102A模块播放音频&#xff0c;那如果我们将两者结合起来&#xff0c;将麦克风采集到的音频通过PCM5102A播放&#xff0c;是不是就可以做一个扩音器了呢…...

C++ 基础特性深度解析

目录 引言 一、命名空间&#xff08;namespace&#xff09; C 中的命名空间​ 与 C 语言的对比​ 二、缺省参数​ C 中的缺省参数​ 与 C 语言的对比​ 三、引用&#xff08;reference&#xff09;​ C 中的引用​ 与 C 语言的对比​ 四、inline&#xff08;内联函数…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

逻辑回归暴力训练预测金融欺诈

简述 「使用逻辑回归暴力预测金融欺诈&#xff0c;并不断增加特征维度持续测试」的做法&#xff0c;体现了一种逐步建模与迭代验证的实验思路&#xff0c;在金融欺诈检测中非常有价值&#xff0c;本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用

在工业制造领域&#xff0c;无损检测&#xff08;NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统&#xff0c;以非接触式光学麦克风技术为核心&#xff0c;打破传统检测瓶颈&#xff0c;为半导体、航空航天、汽车制造等行业提供了高灵敏…...

深入浅出Diffusion模型:从原理到实践的全方位教程

I. 引言&#xff1a;生成式AI的黎明 – Diffusion模型是什么&#xff1f; 近年来&#xff0c;生成式人工智能&#xff08;Generative AI&#xff09;领域取得了爆炸性的进展&#xff0c;模型能够根据简单的文本提示创作出逼真的图像、连贯的文本&#xff0c;乃至更多令人惊叹的…...