Java面试之单例模式浅谈

news2025/2/25 3:07:16

单例模式是Java面试中常会问到的一个问题,众所周知,单例模式分为两大部分:饿汉模式和懒汉模式。但是,如果当面试官问道关于单例模式的话,如果你只答出这两种模式,且懒汉模式还是最基础最简陋版的话,那么你可能就要悲剧了。

当被问到单例模式的时候我们到底需要知道哪些知识点呢?接下来我根据我所掌握的知识点进行一些总结,希望对大家能够有所帮助。

一、饿汉模式:

其实从名字就可以辨认出,该模式的写法是在定义私有全局变量时直接初始化变量,饿汉么,既然已经饿了,那就不管什么赶紧吃了,附代码如下:

private static Singleton singleton = new Singleton();

public static Singleton backSingletonAction() {

return singleton;

     }

该模式最大的问题是,私有全局变量singleton的创建时机问题,由于被static修饰,当class被加载的时候,singleton就会被创建出来,那么就有可能造成空间浪费,一个单例类就会存在一个对象,那么如果有100个单例类就回有100个对象,但是有可能只用到1个,这种情况下就会有99个对象的空间被浪费,这就是饿汉模式最大的问题。

二、懒汉模式:

顾名思义,这种模式的单例比较懒,只有在用到的时候对象才会被创建出来,可以很大程度的节约空间。懒汉模式的实现由很多种,接下来我就从最基础的写法开始进行分析。

1、最简陋的懒汉模式:

private static Singleton singleton = null;

    public static Singleton backSingletonAction() {

if (null == singleton) {

singleton = new Singleton();

}

return singleton;

}

如你所见,这个就是最简陋的懒汉模式单例的书写方法,为什么说是最简陋的单例模式?线程安全!没错,就是存在线程安全问题。当多条线程对backSingletonAction进行访问的时候,就可能造成在singleton成功创建出对象之前有多条线程进入到if判断中,从而造成singleton对象被多次赋值的问题。针对该问题提出设想,这种写法的问题原因是线程不安全,那么让它线程安全不就好了么!如何做到线程安全?当然是使用synchronized了,于是就有了第二种方案:

2、最慢的线程安全的懒汉模式:

private static Singleton singleton = null;

    public static synchronized Singleton backSingletonAction() {

if (null == singleton) {

singleton = new Singleton();

}

return singleton;

}

这种单例的书写方式,由于添加了synchronized关键字,所以线程是安全的,但是也是由于synchronized关键字造成了backSingletonAction加锁的问题,每有一条线程需要访问backSingletonAction时就需要进行锁的竞争,没有竞争到锁的线程需要在锁外部进行等待,等待当前持有锁的线程执行完锁内全部的内容后将锁释放,这样相当于将单例模式中backSingletonAction方法变为了单线程模式,这对于速度是有很大的影响。所以针对这个问题再次进行优化。

3、相对完美版的懒汉模式:

  private static Singleton singleton = null;

  public static Singleton backSingletonAction() {

if (null == singleton) {

synchronized (Singleton.class) {

if (null == singleton) {

singleton = new Singleton();

}

}

}

return singleton;

}

这种单例的书写模式,针对之前的方法进行的改进,使用了双重检查锁的方式,当多条线程第一时间访问到backSingletonAction方法时,进行第一条if语句的判断,此时由于singleton还未初始化,所以if条件为true,均可进入到if条件中,但是,在进入第一层if条件后的线程将会对synchronized进行竞争,只有一条线程可以竞争到锁并进入,那么为什么在锁中仍然要添加if条件判断呢?那是因为在singleton为初始化时进入第一层if的条件可能过多,所以导致在synchronized关键字竞争中仍有很多线程在第一层if内和synchronized外进行等待,当第一条进入synchronized的线程执行完synchronized内部的代码后,若对后续竞争synchronized的线程不进行if判断还是会造成重复创建的问题。

其实这样看起来就已经可以完成这个懒汉模式的单例了,但是为什么这个模式的单例会被称之为相对完整版的懒汉模式?那是因为这种懒汉模式的单例仍然不够完美,我们还可以对其进行优化。

4、完美版懒汉模式单例:

private static volatile Singleton singleton = null;

    public static Singleton backSingletonAction() {

if (null == singleton) {

synchronized (Singleton.class) {

if (null == singleton) {

singleton = new Singleton();

}

}

}

return singleton;

}

为什么说这种写法是完美版的懒汉模式?4和3的区别又在哪里?其实细心地小伙伴已经看出来了,这一版本其实只是在定义singleton的时候加了volatile进行修饰。

volatile作用有两个:

1、禁止指令重排序

2、禁止CPU缓存

而这里使用了volatile,主要是要利用volatile进制指令重排序的功能。

这里可能需要讲一下指令重排序是什么(如果知道的可以将这一部分略过):

在java程序执行过程中,JVM虚拟机可能会根据代码进行执行顺序的优化,即在不影响运行结果的前提下,下一行的代码可能会优于当前行的代码进行执行,例:

①int a = 0;

②String b = "hello";

③System.out.println(a);

④System.out.println(b);

在执行①和②语句上没有必然联系,b="hello"并不依赖于a=0,所以①和②在执行过程中有可能会被JVM优化而导致先执行②再执行①,但是①和③,②和④之间是有因果关系的,所以JVM不会对①和③,②和④的执行顺序进行改变,当代码执行顺序发生变化时,就是指令重排序。

为什么指令重排序会对3的书写方式造成影响?这又涉及到了JVM创建对象的步骤问题,当JVM创建对象时主要分以下三步:

1)开辟内存空间(new指令)----此时已有内存地址了

2)给对象初始化----只是对象成员变量去初始化默认值

3)将堆空间的内存地址(即引用地址)赋值给栈空间的本地变量表中的引用(reference)

在这三步中2)和3)就有可能发生指令重排序,那么当这种指令重排序发生了,并且很幸运此时,由于时间片的切换,切换到第一步if判断的线程时,会发生什么?此时变量是已经存在引用了,所以null==singleton的判断为false,所以会直接return  singleton,但是此时的singleton还未初始化默认值,考虑一下接下来会发生的恐怖事情吧。这种情况发生概率极低且不可复现,但是不代表没有。

所以利用volatile进制对singleton的指令重排序,就可以避免这种事情的发生。

5、静态内部类实现懒汉模式单例

private static class SingletonFactory {

private static Singleton singleton = new Singleton();

private SingletonFactory(){};

public static Singleton backSingleton() {

return singleton;

}

}

public static Singleton backSingletonAction() {

return SingletonFactory.backSingleton();

}

    静态内部类是利用了,类加载的时候不会加载类的静态内部类,但是会加载其私有静态成员变量的原理实现的,程序启动时,加载单例类,由于SingletonFactory为其静态内部类,所以不会被加载,从而不会加载singleton,当调用backSingletonAction获取单例对象时,则会加载SingletonFactory,在加载SingletonFactory时,由于singleton是SingletonFactory的静态成员变量,所以会加载singleton并初始化。


    其实无论是静态内部类还是双重检查锁格式,仍然是有缺陷的,因为这两种书写格式是可能被利用反射或序列化破坏其单例格式,创建出不唯一的对象,如果不想被其他人通过反射或序列化的方式进行破坏,那么可以选用枚举方式来实现单例,枚举由于继承自抽象类ENUM,所以不能通过反射创建,想知道如何通过枚举创建单例模式其实可以再网上找一找,很多。


    最后,希望大家无论是在工作中还是在面试时都能取得更好的成绩。



喜欢的朋友记得点赞、收藏、关注哦!!!

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

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

相关文章

零距离对接Modnbus转Profinet网关连接伺服与PLC

本研究案例详细阐述了开疆智能Modbus转Profinet网关KJ-PNG-201在实现HD3-L系列通用伺服驱动器与PLC互联中的应用。所涉及的设备包括西门子S7-1200 PLC、Modbus转Profinet网关以及HD3-L系列通用伺服驱动器。通过网关转换MODBUS协议的伺服驱动器与PROFINET协议的PLC之间的通信&am…

集成方案 | Docusign + 泛微,实现全流程电子化签署!

本文将详细介绍 Docusign 与泛微的集成步骤及其效果,并通过实际应用场景来展示 Docusign 的强大集成能力,以证明 Docusign 集成功能的高效性和实用性。 在现代企业运营中,效率和合规性是至关重要的。泛微作为企业级办公自动化和流程管理的解决…

CTF-WEB: php-Session 文件利用 [第一届国城杯 n0ob_un4er 赛后学习笔记]

step 1 搭建容器 教程 A5rZ 题目 github.com Dockerfile 有点问题,手动修复一下 FROM php:7.2-apacheCOPY ./flag /root COPY ./readflag / COPY ./html/ /var/www/html/ COPY ./php.ini /usr/local/etc/php/php.ini COPY ./readflag /readsecretRUN chmod 755 /var/www…

融资融券哪家证券公司利率3.8%,融资融券交易流程科普

融资融券哪个券商合适主要考虑券商业务实力与佣金费率等相关因素。 业务方面,一些大型上市券商业务更加成熟。这些券商在融资融券市场中占据重要地位,资金充足,券源丰富,能够满足投资者多样化的投资需求。 融资融券的利率和佣金…

Java 使用 Redis 实现微博热搜功能

在社交平台上,热搜功能是一个非常重要的组成部分。它展示了当前最热门的话题,帮助用户迅速了解最受关注的事件。在微博等平台上,热搜榜单通常是实时变化的,可能会根据用户的互动数据(如搜索频次、点赞量、评论数等&…

《庐山派从入门到...》IDE启动

《庐山派从入门到...》IDE启动 《庐山派从入门到...》IDE启动 IDE(Integrated Development Environment),即集成开发环境,是一种软件应用程序,旨在为软件开发人员提供一个全面的工具集合,以便可以更高效地编…

打电话玩手机识别-支持YOLO,COCO,VOC格式的标记,超高识别率可检测到手持打电话, 非接触式打电话,玩手机自拍等

打电话玩手机识别-支持YOLO,COCO,VOC格式的标记,超高识别率可检测到手持打电话, 非接触式打电话,玩手机自拍等1275个图片。 手持打电话: 非接触打电话 玩手机 数据集下载 yolov11:https://download.csdn…

【智体OS】官方上新发布智体电视:基于rtpc和rttouchpad实现智体电视的手机遥控-可安装任意PC应用用于智体电视

【智体OS】官方上新发布智体电视:基于rtpc和rttouchpad实现智体电视的手机遥控-可安装任意PC应用用于智体电视 dtns.network是一款主要由JavaScript编写的智体世界引擎(内嵌了three.js编辑器的定制版-支持以第一视角浏览3D场馆),…

Flink+Paimon实时数据湖仓实践分享

随着 Paimon 近两年的推广普及,使用 FlinkPaimon 构建数据湖仓的实践也越来越多。在 Flink 实时数据开发中,对于依赖大量状态 state 的场景,如长周期的累加指标计算、回撤长历史数据并更新等,使用实时数仓作为中间存储来代替 Flin…

Stable Diffusion本地部署:从零开始的完整指南

1、引言 Stable Diffusion是计算机视觉领域的一个生成式大模型,能够进行文生图(txt2img)和图生图(img2img)等图像生成任务。它利用深度学习技术,特别是RealisticVision v2.0模型,能够创造出接近…

鸿蒙元服务上架

鸿蒙元服务上架 一、将代码打包成 .app 文件1. 基本需求2. 生成密钥和证书请求文件3. 申请发布证书4. 申请发布Profile5. 配置签名信息6. 更新公钥指纹7. 打包项目成 .app 文件 二、发布元服务1. 进入应用信息页面2. 上传软件包3. 配置隐私协议4. 配置版本信息5. 提交审核&…

【论文阅读】PRIS: Practical robust invertible network for image steganography

内容简介 论文标题:PRIS: Practical robust invertible network for image steganography 作者:Hang Yang, Yitian Xu∗, Xuhua Liu∗, Xiaodong Ma∗ 发表时间:2024年4月11日 Engineering Applications of Artificial Intelligence 关键…

day2 数据结构 结构体的应用

思维导图 小练习: 定义一个数组,用来存放从终端输入的5个学生的信息【学生的信息包含学生的姓名、年纪、性别、成绩】 1>封装函数 录入5个学生信息 2>封装函数 显示学生信息 3>封装函数 删除第几个学生信息,删除后调用显示学…

特征交叉-CAN学习笔记代码解读

一 核心模块coaction 对于每个特征对(feature_pairs)weight, bias 来自于P_inductionP_fead是MLP的input 举个例子:如果是用户ID和产品ID的co-action,且产品ID是做induction,用户ID是做feed。 step1 用户ID/产品ID都先形成一个向量&#xf…

EfficientNet与复合缩放理论(Compound Scaling Theory) 详解(MATLAB)

1.EfficientNet网络与模型复合缩放 1.1 EfficientNet网络简介 1.1.1 提出背景、动机与过程 EfficientNet是一种高效的卷积神经网络(CNN),由Google的研究团队Tan等人在2019年提出。EfficientNet的设计目标是提高网络的性能,同时减…

SQL语句在MySQL中如何执行

MySQL的基础架构 首先就是客户端,其次Server服务层,大多数MySQL的核心服务都在这一层,包括连接、分析、优化、缓存以及所有的内置函数(时间、日期、加密函数),所有跨存储引擎功能都在这一层实现&#xff1…

开源低代码平台-Microi吾码-表单控件数据源绑定配置

表单控件数据源绑定配置 平台简介普通数据源数据源引擎Sql数据源通过其它字段来动态绑定数据源关于绑定数据源后的显示字段和存储字段 平台简介 技术框架:.NET8 Redis MySql/SqlServer/Oracle Vue2/3 Element-UI/Element-Plus平台始于2014年(基于Av…

Y3编辑器文档4:触发器1(对话、装备、特效、行为树、排行榜、不同步问题)

文章目录 一、触发器简介1.1 触发器界面1.2 ECA语句编辑及快捷键1.3 参数设置1.4 变量设置1.5 实体触发器1.6 函数库与触发器复用 二、触发器的多层结构2.1 子触发器(在游戏内对新的事件进行注册)2.2 触发器变量作用域2.3 复合条件2.4 循环2.5 计时器2.6…

Redis原理—4.核心原理摘要

大纲(9870字) 1.Redis服务器的Socket网络连接建立 2.Redis多路复用监听与文件事件模型 3.基于队列串行化的文件事件处理机制 4.完整的Redis Server网络通信流程 5.Redis串行化单线程模型为什么能高并发 6.Redis内核级请求处理流程与原理 7.Redis通信协议与内核级请求数据…

轻量级日志管理平台:Grafana Loki搭建及应用(详细篇)

前言 Grafana Loki是Grafana Lab团队提供的一个水平可扩展、高可用性、多租户的日志聚合系统,与其他日志系统不同的是,Loki最初设计的理念是为了为日志建立标签索引,而非将原日志内容进行索引。 现在目前成熟的方案基本上都是:L…