金三银四面试题(二十):单例模式知多少?

news2025/2/7 4:35:38

设计模式也是面试中的热门考题,基本这个部分都是问问你知不知道XXX设计模式,有什么用,优缺点,然后再现场手写一个demo。很多时候是和spring一起考的,问问你知不知道spring框架用了哪些设计模式。今天我们来先看看单例模式。

什么是单例模式

单例模式是一种设计模式,用于确保类在应用程序中只有一个实例,并提供一个全局访问点来访问该实例。单例模式通常用于那些需要全局状态或共享资源的情况,以确保整个应用程序中只有一个实例存在,从而避免不必要的资源消耗和冲突。例子,一个应用的日志记录器(Logger)。全局一个日志器记录即可,不需要多个。

单例模式的特点包括:

  1. 私有构造函数:单例类的构造函数被设为私有,以防止外部直接创建对象实例。

  2. 静态方法或静态变量:提供一个静态方法或静态变量来访问该类的唯一实例。

  3. 延迟实例化:有时单例对象不会在应用程序启动时立即创建,而是在第一次被请求时才进行实例化。

  4. 线程安全性:在多线程环境中,需要考虑单例对象的线程安全性,确保在并发情况下也能正确地返回唯一实例。

使用单例模式的优点包括:

  • 节省资源:由于只有一个实例存在,可以避免创建多个对象所带来的资源浪费。
  • 提供全局访问点:可以通过单例对象的全局访问点方便地获取到该实例,使得全局状态或共享资源的管理更加简单。
  • 确保一致性:由于只有一个实例存在,可以确保整个应用程序中对该实例的状态保持一致。

然而,使用单例模式也可能带来一些缺点,如增加了代码的耦合性、对单例对象的依赖性过强等。因此,在使用单例模式时需要权衡利弊,并根据实际情况慎重考虑。

手写单例

可能这会需要你手写一个单例模式,单例模式有很多种写法,懒汉模式,饿汉模式,双重检查模式等。

懒汉模式

懒汉模式的懒就在于就是用的时候再去创建对象,否则什么都不做

public class LazySingleton {
    // 私有静态变量,用于保存唯一的实例
    private static LazySingleton instance;

    // 私有构造函数,防止外部直接创建对象实例
    private LazySingleton() {
        // 初始化操作
    }

    // 公共静态方法,用于获取唯一的实例
    public static LazySingleton getInstance() {
        // 延迟实例化,只有在第一次调用时才创建实例
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }

    // 其他方法
    public void doSomething() {
        // 执行其他操作
    }
}

懒汉式单例模式的写法由于new和赋值操作的非原子性所以该写法非线程安全.

饿汉模式

饿汉模式就是提前就已经加载好的静态static 对象

public class EagerSingleton {
    // 私有静态变量,用于保存唯一的实例,并在类加载时就进行初始化
    private static final EagerSingleton instance = new EagerSingleton();

    // 私有构造函数,防止外部直接创建对象实例
    private EagerSingleton() {
        // 初始化操作
    }

    // 公共静态方法,用于获取唯一的实例
    public static EagerSingleton getInstance() {
        return instance;
    }

    // 其他方法
    public void doSomething() {
        // 执行其他操作
    }
}

饿汉式单例模式的写法:线程安全,但饿汉模式的主要缺点是如果该单例对象在应用程序中没有被使用到,那么可能会造成资源的浪费。因为在类加载时就创建了实例,即使在后续没有被使用到,该实例也会一直存在于内存中。

双重检查模式

双重检查模式就是两次检查避免多线程造成创建了多个对象。也是一种在懒汉模式的基础上改进的线程安全的单例模式。它通过双重检查锁定来确保在多线程环境下只创建一个实例。

public class DoubleCheckedSingleton {
    // 使用 volatile 关键字确保 instance 变量的可见性
    private static volatile DoubleCheckedSingleton instance;

    // 私有构造函数,防止外部直接创建对象实例
    private DoubleCheckedSingleton() {
        // 初始化操作
    }

    // 公共静态方法,用于获取唯一的实例
    public static DoubleCheckedSingleton getInstance() {
        // 双重检查锁定,确保在多线程环境下只有一个实例被创建
        if (instance == null) {
            synchronized (DoubleCheckedSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedSingleton();
                }
            }
        }
        return instance;
    }

    // 其他方法
    public void doSomething() {
        // 执行其他操作
    }
}

这里面试官可能问你,可不可以去掉这个volatile关键字,答案是不可以,volatile 关键字的作用是确保变量的可见性和禁止指令重排序,否则可能会出现线程安全问题。
所以,双检锁单例模式的写法:线程安全。

这就结束了吗?

等等,加了volatile的双重检查看似没问题,难道这就一定可靠吗?使用 Java 的反射机制可以破坏传统的单例模式实现。通过反射,可以访问类的私有构造函数,并强制创建多个对象实例,从而违反了单例模式的原则。

import java.lang.reflect.Constructor;

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {
        // 私有构造函数
    }

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

    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getInstance();
        Singleton singleton2 = null;

        try {
            // 使用反射获取私有构造函数
            Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
            // 设置可访问私有构造函数
            constructor.setAccessible(true);
            // 强制创建多个实例
            singleton2 = constructor.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("singleton1: " + singleton1.hashCode());
        System.out.println("singleton2: " + singleton2.hashCode());
        System.out.println("Are they the same instance? " + (singleton1 == singleton2));
    }
}

那要怎么办? 《Effective Java》中曾经提到过,枚举单例是一种线程安全且简洁的单例模式实现方式,它基于枚举类型的特性,在Java中保证了单例实例的唯一性。枚举类型的每个枚举常量都是单例对象,且在枚举类型被加载时就被初始化。

public enum EnumSingleton {
    INSTANCE; // 唯一的枚举常量

    // 可以添加其他成员变量和方法
    private int data;

    public int getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }

    // 可以在枚举类中添加构造函数,但必须是私有的
    private EnumSingleton() {
        this.data = 0;
    }
}

在上面的示例中,EnumSingleton 是一个枚举类型,其中只有一个枚举常量 INSTANCE。由于枚举类型的特性,在类加载时,INSTANCE 常量就会被初始化为单例对象,因此无需担心多线程下的并发问题。

通过调用 EnumSingleton.INSTANCE 就可以获取到该单例对象,例如:

EnumSingleton singleton = EnumSingleton.INSTANCE;

这样就可以确保在整个应用程序中只存在一个 EnumSingleton 实例。

枚举单例的优点包括:

  • 线程安全:枚举类的实例在类加载时就被创建,保证了线程安全性。
  • 简洁:使用枚举类型实现单例模式非常简洁,不需要手动编写单例模式的代码。

因此,如果在Java中实现单例模式,推荐使用枚举类型来实现。

总结

以上就是单例模式的全部内容了,希望能帮助到大家。

在这里插入图片描述

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

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

相关文章

免费SSL证书安全吗?和付费的有什么区别?

SSL&#xff08;安全套接层&#xff09;证书是一种数字证书&#xff0c;用于在Web服务器和浏览器之间建立加密链接&#xff0c;以保护在线交易的安全。这种技术可以防止数据被窃取或篡改&#xff0c;从而保护用户的隐私和安全。 免费SSL证书与付费SSL证书在安全性方面存在一定…

java的Spring XML和注解解析深入理解

正文 熟悉IOC体系结构 要学习Spring源码&#xff0c;我们首先得要找准入口&#xff0c;那这个入口怎么找呢&#xff1f;我们不妨先思考一下&#xff0c;在Spring项目启动时&#xff0c;Spring做了哪些事情。这里我以最原始的xml配置方式来分析&#xff0c;那么在项目启动时&a…

论文略读:SWE-bench: Can Language Models Resolve Real-world Github Issues?

iclr 2024 oral reviewer评分 5668 现有的语言模型&#xff08;LMs&#xff09;的基准测试已经饱和&#xff0c;无法捕捉到最先进的语言模型能做什么和不能做什么的前沿。 ——>要具有挑战性的基准测试论文引入了SWE-bench 在现实软件工程环境中评估语言模型的基准测试 ​​…

Spring Boot+Mybatis+DM数据库

达梦数据库(DM Database)是武汉达梦数据库股份有限公司研发的新一代大型通用关系型国产数据库&#xff0c;全面支持 SQL 标准和主流编程语言接口/开发框架。行列融合存储技术&#xff0c;在兼顾 OLAP 和 OLTP 的同时&#xff0c;满足 HTAP 混合应用场景。 在公司项目开发过程中…

E9:拦截流程系统管理员操作记录,流转记录不显示(ECODE)

效果&#xff1a; ecodeSDK.rewriteApiDataQueueSet({fn: (url, params, data) > {const { hash } window.location;if (!hash.startsWith(#/main/workflow/req)) return false; // 判断页面地址if (!ecCom.WeaTools.Base64) return false; // 完整组件库加载完成if (!W…

【RabbitMQ】RabbitMQ基础认识

文章目录 前言初识MQSpringAMQP如何首发消息&#xff1f;消费者交换机Fanout&#xff1a;广播Direct交换机Topic交换机声明队列和交换机 总结 前言 微服务一旦拆分&#xff0c;必然涉及到服务之间的相互调用&#xff0c;目前我们服务之间调用采用的都是基于OpenFeign的调用。这…

【五十一】【算法分析与设计】KMP 算法,KMP 算法的作用,KMP 算法流程,KMP 算法证明,KMP 算法代码

目录 KMP 算法的作用&#xff0c;解决的问题 KMP 算法的流程 Next 数组 KMP 算法正式过程 KMP 算法的证明过程 Next 数组的求法 Next 数组求法的证明过程 KMP 算法代码 结尾 KMP 算法的作用&#xff0c;解决的问题 1. 首先给你一个字符串 str&#xff0c;然后又给你…

酷开系统丨酷开科技打造P9系列智能投影,让智能化更进一步

近些年&#xff0c;随着科技的进步&#xff0c;家用投影仪已经成为家庭娱乐中不可或缺的一部分。尤其对年轻人来说&#xff0c;他们更喜欢在巨幕上看电影、玩游戏或听歌唱歌&#xff0c;投影仪在巨幕上的光影效果确实能带来更好的沉浸感体验&#xff0c;但这也是需要强大的系统…

OpenHarmony实战开发-页面深色模式适配。

介绍 本示例介绍在开发应用以适应深色模式时&#xff0c;对于深色和浅色模式的适配方案&#xff0c;采取了多种策略如下&#xff1a; 1. 固定属性适配&#xff1a;对于部分组件的颜色属性&#xff0c;如背景色或字体颜色&#xff0c;若保持不变&#xff0c;可直接设定固定色值…

零基础也可以学习的医疗设备维修技能

零基础也可以学习的维修技能 解锁工程师的隐藏潜能&#xff01; 您是否曾因维修问题而感到束手无策&#xff1f; 彩虹医疗影像培训课程不仅提供技能&#xff0c; 更能为您提供自信。不再需要依赖他人&#xff0c; 您将成为故障排查的行家。迎接更具挑战性的机会&#xff0…

C#引用外部组件的常用方法

我们在开发程序过程中&#xff0c;时常会使用到第三方组件&#xff0c;比如一些通信、UI组件等。常用的引用方法有下面几种。 01 NuGet引用 NuGet是.NET的一个包管理平台&#xff0c;很多开源组件会通过NuGet进行管理和发布。比如我们常用的S7NetPlus等。 从NuGet中引用组件…

鸿蒙OS开发指导:【应用包签名工具】

编译构建 该工具基于Maven3编译构建&#xff0c;请确认环境已安装配置Maven3环境&#xff0c;并且版本正确 mvn -version下载代码&#xff0c;命令行打开文件目录至developtools_hapsigner/hapsigntool&#xff0c;执行命令进行编译打包 mvn package编译后得到二进制文件&…

(python)远程操作模块-Paramiko

目录 前言 安装 流程 范例 优点 缺点 需要注意的事项 前言 Paramiko 是一个用于 Python 的模块&#xff0c;用于实现 SSH 客户端和服务器。使用 Paramiko&#xff0c;你可以在 Python 中进行 SSH 连接&#xff0c;并执行远程命令、传输文件等操作。 安装 pip install p…

[leetcode] max-area-of-island

. - 力扣&#xff08;LeetCode&#xff09; 给你一个大小为 m x n 的二进制矩阵 grid 。 岛屿 是由一些相邻的 1 (代表土地) 构成的组合&#xff0c;这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0&#xff08;代表水&…

模仿SpringSecurity配置文件的写法对mybatisPlus查询方法的改造

使用mybatisPlus查询数据的传统流程是&#xff1a;Autowired mapper对象。new Wrapper 一通乱set Wrapper ,select xxx。但实际开发中&#xff0c;还有很大的改进空间&#xff0c;一是一些脆弱的参数设置有多处&#xff0c;得不到妥善维护&#xff0c;二是代码编写丑陋难看。因…

重生奇迹mu怎么转职

一转&#xff1a;一转的等级是180级&#xff0c;要求就是材料帝王之书收集2本&#xff0c;还需要7万金币就可以直接转职了&#xff0c;帝王之术就是任务了&#xff0c;在任务那里可以看到转职任务&#xff0c;我们只要做了转职任务就可以收集完了&#xff0c;一转分别叫做&…

springboot直接运行 java-jar

一、问题 springboot 为什么能通过java-jar运行&#xff0c;打包的时候也把tomcat打了吗&#xff1f; 二、解答 Spring Boot 应用程序通常打包成可执行的 JAR 文件&#xff0c;并且可以通过 java -jar 命令来运行。这是因为 Spring Boot 在打包时会将应用程序本身和嵌入的 T…

振弦式渗压计的安装与防护:在水工建筑物中的关键应用

振弦式渗压计&#xff0c;作为一种高效的孔隙水压力或液体液位测量工具&#xff0c;广泛应用于水工建筑物、基岩内、测压管、钻孔、堤坝、管道和压力容器内。其安装和防护工作至关重要&#xff0c;直接关系到测量数据的准确性和仪器的使用寿命。本文将重点探讨振弦式渗压计在填…

武汉星起航引领跨境电商新浪潮,与亚马逊携手共拓全球市场

在全球贸易日益繁荣的当下&#xff0c;跨境电商行业正迎来前所未有的发展机遇。武汉星起航电子商务有限公司&#xff0c;作为跨境电商领域的佼佼者&#xff0c;凭借其前瞻性的战略布局和强大的运营能力&#xff0c;与亚马逊跨境电商平台紧密合作&#xff0c;共同推动全球贸易的…

【C语言】<动态内存管理>我的C语言终末章

&#xff1c;动态内存管理&#xff1e; 1. 为什么要有动态内存分配2. malloc和free2.1 malloc2.2 free 3. calloc和realloc3.1 calloc3.2 realloc 4.常见的动态内存错误4.1 对NULL指针的解引用操作4.2 对动态开辟空间的越界访问4.3 对非动态开辟内存使用free释放4.4 使用free释…