⛳ 面试题-单例模式会存在线程安全问题吗?

🎍目录

  • ⛳ 面试题-单例模式会存在线程安全问题吗?
    • 🎨 一、单例模式-简介
    • 🚜 二、饿汉式
    • 🐾 三、懒汉式
      • 🎯 3.1、懒汉式:在调用 getInstance 的时候才创建对象。(线程不安全)
      • 🏓 3.2、改造1:对懒汉式进行加锁改造(线程安全)
      • 🔮 3.3、改造2:对懒汉式继续改造。(线程不安全)
      • 🏀 3.4、改造3(改造成功):对懒汉式再次改造。(线程安全)
      • 3.5、总结
    • 🏭 四、内部静态类
      • 🧰 4.1、反射攻击
      • 🧬 4.2、反序列化攻击
    • ⭐ 五、枚举
    • 📢 六、总结

⛳ 面试题-单例模式会存在线程安全问题吗?

🎨 一、单例模式-简介

单例模式是 Java 中常用的设计模式之一,属于设计模式三大类中的创建型模式。在运行期间,保证某个类仅有一个实例,并提供一个访问它的全局访问点。单例模式所属类的构造方法是私有的,所以单例类是不能被继承的。实现线程安全的单例模式有以下几种方式有:饿汉式、懒汉式、懒汉式改良版(双重同步锁),内部静态类、枚举;

🚜 二、饿汉式

天生是线程安全的 !(但是无法应对反射、序列化的形式)

饿汉式在类创建的同时,就已经创建好了一个静态的对象供系统使用,以后不在改变;

public class Singleton {private static Singleton instance = new Singleton();private Singleton() {  // 私有的构造方法}public static Singleton getInstance() {return instance;}}

这是实现一个安全的单例模式的最简单粗暴的写法,所以称之为饿汉式;

因为肚子饿了,想要马上吃到东西,不想等待生产时间。在类被加载的时候就把 Singleton 实例给创建出来,以后不在改变。

饿汉式的优点和缺点:

  • 优点:实现简单、线程安全,调用效率高(无锁,且对象在类加载时就已创建,可直接使用);
  • 缺点:可能在还不需要此实例的时候就已经把实例创建出来了,不能延时加载(在需要的时候才创建对象)、使用反射,序列化创建对象依然可以不是单例的;

🐾 三、懒汉式

🎯 3.1、懒汉式:在调用 getInstance 的时候才创建对象。(线程不安全)

public class Singleton {  private static Singleton instance=null;  private Singleton() {};  public static Singleton getInstance(){  if(instance==null){//可能会有多个线程进入代码块,造成实例化多个对象instance=new Singleton();  }  return instance;  }  }

🏓 3.2、改造1:对懒汉式进行加锁改造(线程安全)

public class Singleton {  private static Singleton instance=null;  private Singleton() {};  public static synchronized Singleton getInstance(){  if(instance==null){  instance=new Singleton();  }  return instance;  }  
}

但是这种方式并不推荐,因为效率想对较低,每个线程在执行 getInstance 的时候都要进行同步。而如果 instance 已经实例化了可以直接返回,还需要进行改造;

🔮 3.3、改造2:对懒汉式继续改造。(线程不安全)

public class Singleton {  private static Singleton instance=null;  public static Singleton getInstance() {  if (instance == null) {  synchronized (Singleton.class) {  instance = new Singleton();  }  }  return instance;  }  
}

这种方式是线程不安全的,假如 A,B 两个线程同时进入到了 if(instance == null)的代码块, A 线程拿到了锁进入 synchronized代码块,对 instance 进行实例化,结束并释放锁,B 线程便拿到锁,依然会进入到 synchronized代码块对 instance 进行实例化。那么这就对 instance 进行了两次实例化。出现了线程安全的问题。

🏀 3.4、改造3(改造成功):对懒汉式再次改造。(线程安全)

这种代码书写方式也称为 双重同步锁

public class Singleton {  private static volatile Singleton instance=null;  private Singleton(){}public static Singleton getInstance() {  if (instance == null) {  synchronized (Singleton.class) { //在(3.3)的基础上多了一次判断,避免了线程安全问题if (instance == null) {  instance = new Singleton();  }}  }  return instance;  }  
}

使用了double-check即check-加锁-check,减少了同步的开销;

在创建第一个对象时候,可能会有线程1,线程2两个线程进入getInstance()方法,这时对象还未被创建,所以都通过第一层check。接下来的synchronized锁只有一个线程可以进入,假设线程1进入,线程2等待。线程1进入后,由于对象还未被创建,所以通过第二层check并创建好对象,由于对象singleton是被volatile修饰的,所以在对singleton修改后会立即将singleton的值从其工作内存刷回到主内存以保证其它线程的可见性。线程1结束后线程2进入synchronized代码块,由于线程1已经创建好对象并将对象值刷回到主内存,所以这时线程2看到的singleton对象不再为空,因此通过第二层check,最后获取到对象。这里volatile的作用是保证可见性,同时也禁止指令重排序,因为上述代码中存在控制依赖,多线程中对控制依赖进行指令重排序会导致线程不安全。

优点:线程安全,可以延时加载,调用效率比锁加在方法上高。

3.5、总结

相比于饿汉式,懒汉式显得没那么 “饿”,在真正需要的时候在去创建实例。

懒汉式的优点和缺点:

  • 优点:线程安全的,可以延时加载。
  • 缺点:调用效率不高(有锁,且需要先创建对象)、使用反射,序列化创建对象依然可以不是单例的

🏭 四、内部静态类

public class Singleton {private Singleton() {}public static Singleton getInstance() {return SingletonFactory.instance;}private static class SingletonFactory {private static Singleton instance = new Singleton();}
}    

静态内部类只有被主动调用的时候,JVM才会去加载这个静态内部类。外部类初次加载,会初始化静态变量、静态代码块、静态方法,但不会加载内部类和静态内部类。

优点:线程安全,调用效率高,可以延时加载。

似乎静态内部类看起来已经是最完美的方法了,其实不是,可以还存在反射攻击和反序列化攻击。

🧰 4.1、反射攻击

public class Singleton {private Singleton() {}public static Singleton getInstance() {return SingletonFactory.instance;}private static class SingletonFactory {private static Singleton instance = new Singleton();}public static void main(String[] args) throws Exception {Singleton singleton = Singleton.getInstance();Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();constructor.setAccessible(true);Singleton newSingleton = constructor.newInstance();System.out.println(singleton == newSingleton);
}}  

运行结果:false

通过结果看,这两个实例不是同一个,违背了单例模式的原则;

🧬 4.2、反序列化攻击

<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.8.1</version>
</dependency>

这个依赖提供了序列化和反序列化工具类。

Singleton 类实现了 java.io.Serializable接口。

public class Singleton implements Serializable {private static class SingletonHolder {private static Singleton instance = new Singleton();}private Singleton() {}public static Singleton getInstance() {return SingletonHolder.instance;}public static void main(String[] args) {Singleton instance = Singleton.getInstance();byte[] serialize = SerializationUtils.serialize(instance); // 序列化为一个数组Singleton newInstance = SerializationUtils.deserialize(serialize); // 通过刚才序列化的数组,进行反序列化System.out.println(instance == newInstance);}}

输出结果:false,表示不是一个实例;

如果要解决 Singleton 类的实力在序列化和反序列化过程中仍然是唯一的,需要添加一个readResolve()方法到 Singleton 类中,以便在反序列化是返回相同的实例。如:

private Object readResolve() throws ObjectStreamException {return getInstance();
}

在 Java 中,readResolve() 方法是一个特殊的方法,用于在对象反序列化过程中控制返回的实例。它主要用于解决单例模式在反序列化时可能出现的问题。

⭐ 五、枚举

最佳的单例实现模式就是枚举模式。写法简单,线程安全,调用效率高,可以天然的防止反射和反序列化调用,不能延时加载。

public enum Singleton {INSTANCE;public void doSomething() {System.out.println("doSomething");}
}    

调用方法:

public class Main {public static void main(String[] args) {Singleton.INSTANCE.doSomething();}}

直接通过Singleton.INSTANCE.doSomething()的方式调用即可。

枚举如何实现线程安全?反编译后可以发现,会通过一个类去继承该枚举,然后通过静态代码块的方式在类加载时实例化对象,与饿汉类似。https://blog.csdn.net/wufaliang003/article/details/81395411

如何做到防止反序列化调用?每一个枚举类型及其定义的枚举变量在JVM中都是唯一的,Java做了特殊的规定,枚举类型序列化和反序列化出来的是同一个对象。

除此之外,枚举还可以防止反射调用。

📢 六、总结

综上,线程安全的几种单例模式比较来看:

枚举(无锁,调用效率高,可以防止反射和反序列化调用,不能延时加载)> 静态内部类(无锁,调用效率高,可以延时加载)> 双重同步锁(有锁,调用效率高于懒汉式,可以延时加载)> 懒汉式(有锁,调用效率不高,可以延时加载)~= 饿汉式(无锁,调用效率高,不能延时加载)

注:只有枚举类型能防止反射和反序列化;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/60627.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ChatGPT癌症治疗“困难重重”,真假混讲难辨真假,准确有待提高

近年来&#xff0c;人工智能在医疗领域的应用逐渐增多&#xff0c;其中自然语言处理模型如ChatGPT在提供医疗建议和信息方面引起了广泛关注。然而&#xff0c;最新的研究表明&#xff0c;尽管ChatGPT在许多领域取得了成功&#xff0c;但它在癌症治疗方案上的准确性仍有待提高。…

leetcode 392. 判断子序列

2023.8.25 本题要判断子序列&#xff0c;可以使用动态规划来做&#xff0c;定义一个二维dp数组。 接下来就是常规的动态规划求解子序列的过程。 给出两种定义dp数组的方法。 二维bool型dp数组&#xff1a; class Solution { public:bool isSubsequence(string s, string t) …

在云原生环境中构建可扩展的大数据平台:方法和策略

文章目录 1. **选择适当的云提供商&#xff1a;**2. **采用容器化和微服务架构&#xff1a;**3. **分层架构设计&#xff1a;**4. **弹性计算资源&#xff1a;**5. **使用分布式计算框架&#xff1a;**6. **数据分区和分片&#xff1a;**7. **使用列式存储&#xff1a;**8. **缓…

qt day 1

this->setWindowIcon(QIcon("D:\\zhuomian\\wodepeizhenshi.png"));//設置窗口的iconthis->setWindowTitle("鵬哥快聊");//更改名字this->setFixedSize(500,400);//設置尺寸QLabel *qlnew QLabel(this);//創建一個標簽ql->resize(QSize(500,20…

【计算机视觉|生成对抗】用于高保真自然图像合成的大规模GAN训练用于高保真自然图像合成的大规模GAN训练(BigGAN)

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;Large Scale GAN Training for High Fidelity Natural Image Synthesis 链接&#xff1a;[1809.11096] Large Scale GAN Training for High Fidelity Natural Image Synthesis (arxiv.org…

Rabbitmq消息积压问题如何解决以及如何进行限流

一、增加处理能力 优化系统架构、增加服务器资源、采用负载均衡等手段&#xff0c;以提高系统的处理能力和并发处理能力。通过增加服务器数量或者优化代码&#xff0c;确保系统能够及时处理所有的消息。 二、异步处理 将消息的处理过程设计为异步执行&#xff0c;即接收到消息…

基于机器学习的fNIRS信号质量控制方法

摘要 尽管功能性近红外光谱(fNIRS)在神经系统研究中的应用越来越广泛&#xff0c;但fNIRS信号处理仍未标准化&#xff0c;并且受到经验和手动操作的高度影响。在任何信号处理过程的开始阶段&#xff0c;信号质量控制(SQC)对于防止错误和不可靠结果至关重要。在fNIRS分析中&…

FreeSWITCH 1.10.10 简单图形化界面5 - 使用百度TTS

FreeSWITCH 1.10.10 简单图形化界面5 - 使用百度TTS 0、 界面预览1、注册百度AI开放平台&#xff0c;开通语音识别服务2、获取AppID/API Key/Secret Key3、 安装百度语音合成sdk4、合成代码5、在PBX中使用百度TTS6、音乐文件-TTS7、拨号规则-tts_command 0、 界面预览 http://…

网络有源号角(50W-100W)社区小区广播 工地语音播报,隧道广播,钢铁广播广播系统

网络有源号角&#xff08;50W-100W&#xff09;社区小区广播 工地语音播报&#xff0c;隧道广播&#xff0c;钢铁广播广播系统 SV-7042T 50W网络有源号角 SV-7042T是深圳锐科达电子有限公司的一款壁挂式网络有源号角&#xff0c;具有10/100M以太网接口&#xff0c;可将网络音…

ceph源码阅读 erasure-code

1、ceph纠删码 纠删码(Erasure Code)是比较流行的数据冗余的存储方法&#xff0c;将原始数据分成k个数据块(data chunk)&#xff0c;通过k个数据块计算出m个校验块(coding chunk)。把nkm个数据块保存在不同的节点&#xff0c;通过n中的任意k个块还原出原始数据。EC包含编码和解…

解密Spring MVC异常处理:从局部到全局,打造稳固系统的关键步骤

&#x1f600;前言 在现代软件开发中&#xff0c;异常处理是不可或缺的一部分&#xff0c;它能够有效地提高系统的稳定性和健壮性。在Spring MVC框架中&#xff0c;异常处理机制起着至关重要的作用&#xff0c;它允许开发者在程序运行过程中捕获、处理和报告异常&#xff0c;从…

Qt/C++编写视频监控系统80-远程回放视频流

一、前言 远程回放NVR或者服务器上的视频文件&#xff0c;一般有三种方式&#xff0c;第一种是调用厂家的SDK&#xff0c;这个功能最全&#xff0c;但是缺点明显就是每个厂家的设备都有自己的SDK&#xff0c;只兼容自家的设备&#xff0c;如果你的软件需要接入多个厂家的&…

【深入解读Redis系列】Redis系列(五):切片集群详解

首发博客地址 https://blog.zysicyj.top/ 系列文章地址[1] 如果 Redis 内存很大怎么办&#xff1f; 假设一台 32G 内存的服务器部署了一个 Redis&#xff0c;内存占用了 25G&#xff0c;会发生什么&#xff1f; 此时最明显的表现是 Redis 的响应变慢&#xff0c;甚至非常慢。 这…

分类预测 | MATLAB实现SSA-CNN-SVM基于麻雀算法优化卷积支持向量机分类预测

分类预测 | MATLAB实现SSA-CNN-SVM基于麻雀算法优化卷积支持向量机分类预测 目录 分类预测 | MATLAB实现SSA-CNN-SVM基于麻雀算法优化卷积支持向量机分类预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现SSA-CNN-SVM基于麻雀算法优化卷积支持向量机分类预测…

数学建模:数据的预处理

&#x1f506; 文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 文章目录 数据预处理数据变换数据清洗缺失值处理异常值处理 数据预处理 数据变换 常见的数据变换的方式&#xff1a;通过某些简单的函数进行数据变换。 x ′ x 2 x ′ x x ′ log ⁡ ( x ) ∇ f ( x k )…

Redis 持久化和发布订阅

一、持久化 Redis 是内存数据库&#xff0c;如果不将内存中的数据库状态保存到磁盘&#xff0c;那么一旦服务器进程退出&#xff0c;服务器中的数据库状态也会消失。所以 Redis 提供了持久化功能&#xff01; 1.1、RDB&#xff08;Redis DataBase&#xff09; 1.1.1 …

第一方支付、第二方支付、第三方支付、第三方支付是什么?

我相信关于支付行业大家多多少少都有一些自己的理解&#xff0c;但是具体的一些名词如标题中的这些&#xff0c;第一方、第二方、第三方支付&#xff0c;到底指的是什么&#xff1f; 第一方支付 也就是现金支付&#xff0c;其本质的意义就是指货币支付&#xff0c;从最早出现货…

猜拳游戏小程序源码 大转盘积分游戏小程序源码 积分游戏小程序源码

简介&#xff1a; 猜拳游戏大转盘积分游戏小程序前端模板源码&#xff0c;一共五个静态页面&#xff0c;首页、任务列表、大转盘和猜拳等五个页面 图片&#xff1a;

Spring与Mybatis集成且Aop整合

目录 一、集成 1.1 集成的概述 1.2 集成的优点 1.3 代码示例 二、整合 2.1 整合概述 2.2 整合进行分页 一、集成 1.1 集成的概述 集成是指将不同的组件、部分或系统组合在一起&#xff0c;以形成一个整体功能完整的解决方案。它是通过连接、交互和协调组件之间的关系来实…

MybatisPlus-插件篇

文章目录 一、前言二、插件1、分页插件2.1.1、引入依赖2.1.1、配置分页插件2.1.3、使用分页方法 2、乐观锁插件2.1、引入依赖2.2、添加版本字段2.3、配置乐观锁插件2.4、执行更新操作 三、总结 一、前言 本文将详细介绍mybatisplus中常用插件的使用。 二、插件 1、分页插件 …