【面试八股总结】单例模式实现详解

news2024/12/25 14:37:25

一、基本概念

        单例设计模式是⼀种确保⼀个类只有⼀个实例,并提供⼀个全局访问点来访问该实例的创建模式。

关键概念:

  • 一个私有构造函数:确保只能单例类自己创建实例
  • 一个私有静态变量:确保只有一个实例,私有静态变量用于保存该类的唯一实例
  • 一个公有静态函数:给使用者提供调用方法

优点:

        有些实例全局只需要⼀个,使用单例模式就可以避免一个全局使用的类,频繁的创建与销毁,耗费系统资源。

二、实现方法

单例模式有两种类型:

  • 懒汉式:在真正需要使用对象时,采取创建该单例类对象
  • 饿汉式:在类加载时已经创建好该单例对象,等待被程序使用

1. 懒汉式(线程不安全)

        懒汉式创建对象方法在程序使用前会先判断该对象是否已经实例化(判断是否为空),若已实例化直接返回该类对象,否则执行实例化。

class Singleton{
    private static Singleton instance;
    private Singleton(){}    // 构造函数为私有,确保外界不可以使用new创建该类实例
    
    public static Singleton GetInstance(){    // 该方法为本类实例的唯一全局访问点
        if (instance == NULL){         // 若实例不存在,则new一个实例,否则返回已有实例
            instance = new Singleton();
        }
        return instance;
    }
};

2. 懒汉式(线程安全) 

        在这里考虑线程安全问题,如果多个线程同时判断instance为空,那么他们都会去实例化一个Singleton对象,就违背了单例模式的原则。为了保证线程安全,考虑加上锁。

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

3. 双重检查锁

        上述代码虽然解决了线程安全问题,但是性能降低了。因为,即使实例已经实例化了, 但是锁还在,每次还是只能拿到锁的线程进入该方法使线程阻塞,等待时间过长。

        将锁的位置改变,并且多加了⼀个检查。也就是,先判断实例是否已经存在,若已经存在了,则不会执行判断方法内的有锁方法了。 而还没有实例化的时候多个线程进去也没有事,因为里面的方法有锁,只会让⼀个线程进⼊最内层方法并实例化实例。如此⼀来,最多也就是第⼀次实例化的时候,会有线程阻塞的情况,后续便不会再有线程阻塞的问题。

class Singleton{
    private volatile static Singleton uniqueInstance;
    private Singleton(){}  
    
    public static Singleton getInstance() {
        if (uniqueInstance== NULL) {    
            synchronized(Singleton.class) {    // 加锁,只有一个线程获得该锁并进行初始化
                if (uniqueInstance== NULL) {         
                    uniqueInstance= new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

为什么使用volatile关键字修饰uniqueInstance变量?

uniqueInstance= new Singleton();

上述一行代码在执行时分为三步:

  1. 为uniqueInstance分配空间;
  2. 初始化uniqueInstance;
  3. 将uniqueInstance指向分配的内存地址。

        采用volatile会禁止JVM的指令重排(指令重排会导致有些协程获取到还没有初始化的实例),保证多线程环境下的安全运行。

4. 饿汉式

        饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可,即:在编码时已经指明马上创建该对象,不需要等待调用时再创建。

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

5. 静态内部类

        当外部类Singleton被加载时,静态内部类SingletonHolder并没有被加载金内存,当调用getInstance()方法时,才会触发SingletonHolder.instance,此时静态内部类才会被加载进内存,并且初始化instance实例。该方法延迟了实例化,节约了资源,且线程安全,性能也提高了。

class Singleton{
    private Singleton(){}    
    
    // 静态内部类持有实例
    private static class SingletonHolder{
        private static final Singleton instance = new Singleton();
    }
    
    // 公告静态方法,返回实例
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}; 

6. 枚举类

        默认枚举实例的创建就是线程安全的,且在任何情况下都是单例。可以防止调用者使用反射、序列化与反序列化机制强制生成多个单例对象,破坏单例模式。

public enum Singleton{
    INSTANCE;
    
    // 可以添加其他方法和属性
    public void doSomething(){
        // 实现...
    }
}

总结:

(1)单例模式常见的写法有两种:懒汉式、饿汉式

  • 懒汉式:在需要用到对象时才实例化对象,正确的实现方式是:Double Check + Lock,解决了并发安全和性能低下问题
  • 饿汉式:在类加载时已经创建好该单例对象,在获取单例对象时直接返回对象即可,不会存在并发安全和性能问题。

(2)在开发中如果对内存要求非常高,那么使用懒汉式写法,可以在特定时候才创建该对象;

        如果对内存要求不高使用饿汉式写法,因为简单不易出错,且没有任何并发安全和性能问题

(3)为了防止多线程环境下,因为指令重排序导致变量报NPE,需要在单例对象上添加volatile关键字防止指令重排序

(4)最优雅的实现方式是使用枚举,其代码精简,没有线程安全问题,且 Enum 类内部防止反射和反序列化时破坏单例。

三、应用场景

单例设计模式适用于以下⼀些场景:

  • 资源共享:当多个模块或系统需要共享某⼀资源时,可以使用单例模式确保该资源只被创建⼀次,避免重复创 建和浪费资源。
  • 控制资源访问:单例模式可以用于控制对特定资源的访问,例如数据库连接池、线程池等。
  • 配置管理器:当整个应用程序需要共享⼀些配置信息时,可以使用单例模式将配置信息存储在单例类中,方便全局访问和管理。
  • 日志记录器:单例模式可以用于创建⼀个全局的日志记录器,用于记录系统中的日志信息。
  • 线程池:在多线程环境下,使用单例模式管理线程池,确保线程池只被创建⼀次,提高线程池的利⽤率。
  • 缓存:单例模式可以⽤于实现缓存系统,确保缓存只有⼀个实例,避免数据不⼀致性和内存浪费。

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

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

相关文章

IDEA自动把接口中的方法注解填充到实现类中,勾选Copy JavaDoc即可

1. 目的 有一个Image接口类,接口中有getUserById方法,方法上有注释,实现类ImageImpl实现Image中的方法时,自动把接口中方法的注释也给带下来 具体案例如下 2. 接口类 有一个getUserById方法,方法上面有注释 3. 实现…

Java常用的API_02(正则表达式、爬虫)

Java正则表达式 七、正则表达式7.1 格式7.1.1 字符类注意字符类示例代码1例2 7.1.2 预定义字符预定义字符示例代码例2 7.1.3 区别总结 7.2 使用Pattern和Matcher类与直接使用String类的matches方法的区别。(1) 使用Pattern和Matcher类示例代码 &#xff…

JVM 之对象的结构与创建

1.对象的创建 1.1类加载 当Java 虚拟机遇到一条字节码 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到 一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那 必须先执行相应的类加载过…

昇思MindSpore学习总结十五 ——基于Mindspore 实现BERT对话情绪识别

1、环境配置 根据实际情况,选择合适版本。 %%capture captured_output # 实验环境已经预装了mindspore2.2.14,如需更换mindspore版本,可更改下面mindspore的版本号 !pip uninstall mindspore -y !pip install -i https://pypi.mirrors.ustc…

用node.js写一个简单的图书管理界面——功能:添加,删除,修改数据

涉及到的模块: var fs require(‘fs’)——内置模块 var ejs require(‘ejs’)——第三方模块 var mysql require(‘mysql’)——第三方模块 var express require(‘express’)——第三方模块 var bodyParser require(‘body-parser’)——第三方中间件 需要…

华为HCIP Datacom H12-821 卷38

1.多选题 下面关于 BGP中的公认属性的描述,正确的是 A、公认必遵属性是所有BGP路由器都识别,且必须存在于Updata消息中心 B、BGP必须识别所有公认属性 C、公认属性分为公认必遵和可选过渡两种 D、公认任意属性是所有BGP造由器都可以识别&#xff0c…

217.贪心算法:加油站(力扣)

代码解决 class Solution { public:int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {int curtotol 0; // 当前累积油量int tatol 0; // 总的油量减去总的花费油量int start 0; // 起始加油站的索引// 遍历所有加油站for (int i 0; i &…

【Android面试八股文】你说ARouter采用APT技术,那么谈一下你对APT技术的理解,还有那些框架是采用APT技术呀?JavaPoet

一、谈一下你对APT技术的理解 1.1 对APT技术的理解 APT(Annotation Processing Tool)是一种在编译期间处理注解的技术,它允许开发者在编译时扫描和处理 Java 源代码中的注解信息,生成额外的源代码、资源文件或者其他文件。以下是对APT技术的一些理解和应用场景: 工作原理…

期货量化交易客户端开源教学第九节——新用户注册

一、新用户注册界面设计&#xff1a; 注册时采用手机号注册&#xff0c;客户端发送新号注册申请由后台做审核&#xff0c;后台审核通过后向注册的手机号发送注册成功的消息。注册过的手机号不能再二次注册。 界面验证代码 private{ Private declarations }FVerf: AnsiString; …

【React Native】做了一个简约的雷达图组件

本文目录 【React Native】做了一个简约的雷达图组件获取组件实现思路用法示例简易用法自定义美化 结语 【React Native】做了一个简约的雷达图组件 最近在使用 react-native 中需要绘制雷达图&#xff0c;没有找到合适的小组件&#xff08;大的图表库未直接提供&#xff0c;需…

【活动预告】Apache IoTDB TsFile 智慧能源应用“上会”啦!

2024 年&#xff0c;站在中国数字经济产业升级和数据要素市场化建设的时代交汇点上&#xff0c;为进一步推动全球数据库产业进步&#xff0c;由中国通信标准化协会、大数据技术标准推进委员会主办的“2024 可信数据库发展大会”将于 2024 年 7 月 16-17 日&#xff0c;在北京朝…

Mac M1安装配置Hadoop+Flink SQL环境

Flink 1.18.1 Hadoop 3.4.0 一、准备工作 系统&#xff1a;Mac M1 (MacOS Sonoma 14.3.1) JDK&#xff1a;jdk1.8.0_381 &#xff08;注意&#xff1a;尽量一定要用JDK8&#xff0c;少用高版本&#xff09; Scala&#xff1a;2.12 JDK安装在本机的/opt/jdk1.8.0_381.jdk/C…

海外ASO:iOS与谷歌优化的相同点和区别

海外ASO是针对iOS的App Store和谷歌的Google Play这两个主要海外应用商店进行的优化过程&#xff0c;两个不同的平台需要采取不同的优化策略&#xff0c;以下是对iOS优化和谷歌优化的详细解析&#xff1a; 一、iOS优化&#xff08;App Store&#xff09; 1、关键词覆盖 选择关…

【公益案例展】中国电信安全大模型——锻造安全行业能量转化的高性能引擎...

‍ 电信安全公益案例 本项目案例由电信安全投递并参与数据猿与上海大数据联盟联合推出的 #榜样的力量# 《2024中国数智产业最具社会责任感企业》榜单/奖项评选。 大数据产业创新服务媒体 ——聚焦数据 改变商业 以GPT系列为代表的大模型技术&#xff0c;展现了人工智能技术与应…

Macos 远程登录 Ubuntu22.04 桌面

这里使用的桌面程序为 xfce, 而 gnome 桌面则测试失败。 1,安装 在ubuntu上&#xff0c;安装 vnc server与桌面程序xfce sudo apt install xfce4 xfce4-goodies tightvncserver 2&#xff0c;第一次启动和配置 $ tightvncserver :1 设置密码。 然后修改配置&#xff1a…

3d为什么删掉模型不能返回?---模大狮模型网

在展览3D模型设计行业中&#xff0c;设计师们经常面临一个关键问题&#xff1a;一旦删除了模型的某些部分&#xff0c;为什么很难或者无法恢复原始状态?这不仅是技术上的挑战&#xff0c;更是设计过程中需要深思熟虑的重要考量。本文将探讨这一问题的原因及其在实际工作中的影…

传输层协议之UDP

1、端口号 我们在应用层创建的套接字&#xff0c;是需要通过bind()接口绑定我们的IP地址与端口号的&#xff0c;这是因为数据从传输层向上交付到应用层时&#xff0c;需要用端口号来查找特定的服务进程。一般在网络通信时&#xff0c;用IP地址标识一台主机&#xff0c;用端口号…

查找PPT中某种字体的全部对应文字

本文章的目的是找到某种字体的文字&#xff0c;而不是替换某种字体的文字&#xff0c;也不是将某种字体全部替换为另外一种文字。 第一步&#xff1a;在PPT中按下ALTF11 出现以下窗口 第二步&#xff1a;点击插入->模块 第三步&#xff1a;将以下代码输入到窗体中 Sub F…

【备战秋招】——算法题目训练和总结day4

【备战秋招】——算法题目训练和总结day4&#x1f60e; 前言&#x1f64c;Fibonacci数列我的题解思路分享代码分享 单词搜索我的题解思路分享代码分享 杨辉三角我的题解思路分享代码分享 总结撒花&#x1f49e; &#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢…

C++的缺省参数、函数重载和引用

缺省参数 缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时&#xff0c;如果没有指定实参 则采⽤该形参的缺省值&#xff0c;否则使⽤指定的实参&#xff0c;缺省参数分为全缺省和半缺省参数。(有些地⽅把 缺省参数也叫默认参数)&#xff0c;要注意的是…