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

2024-04-17 05:52

本文主要是介绍金三银四面试题(二十):单例模式知多少?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

设计模式也是面试中的热门考题,基本这个部分都是问问你知不知道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.chinasem.cn/article/910956

相关文章

2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题

题库来源:安全生产模拟考试一点通公众号小程序 2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题是由安全生产模拟考试一点通提供,流动式起重机司机证模拟考试题库是根据流动式起重机司机最新版教材,流动式起重机司机大纲整理而成(含2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题参考答案和部分工种参考解析),掌握本资料和学校方法,考试容易。流动式起重机司机考试技

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [

模版方法模式template method

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/template-method 超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 上层接口有默认实现的方法和子类需要自己实现的方法

【iOS】MVC模式

MVC模式 MVC模式MVC模式demo MVC模式 MVC模式全称为model(模型)view(视图)controller(控制器),他分为三个不同的层分别负责不同的职责。 View:该层用于存放视图,该层中我们可以对页面及控件进行布局。Model:模型一般都拥有很好的可复用性,在该层中,我们可以统一管理一些数据。Controlller:该层充当一个CPU的功能,即该应用程序

迭代器模式iterator

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/iterator 不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素

《x86汇编语言:从实模式到保护模式》视频来了

《x86汇编语言:从实模式到保护模式》视频来了 很多朋友留言,说我的专栏《x86汇编语言:从实模式到保护模式》写得很详细,还有的朋友希望我能写得更细,最好是覆盖全书的所有章节。 毕竟我不是作者,只有作者的解读才是最权威的。 当初我学习这本书的时候,只能靠自己摸索,网上搜不到什么好资源。 如果你正在学这本书或者汇编语言,那你有福气了。 本书作者李忠老师,以此书为蓝本,录制了全套视频。 试

利用命令模式构建高效的手游后端架构

在现代手游开发中,后端架构的设计对于支持高并发、快速迭代和复杂游戏逻辑至关重要。命令模式作为一种行为设计模式,可以有效地解耦请求的发起者与接收者,提升系统的可维护性和扩展性。本文将深入探讨如何利用命令模式构建一个强大且灵活的手游后端架构。 1. 命令模式的概念与优势 命令模式通过将请求封装为对象,使得请求的发起者和接收者之间的耦合度降低。这种模式的主要优势包括: 解耦请求发起者与处理者

springboot实战学习(1)(开发模式与环境)

目录 一、实战学习的引言 (1)前后端的大致学习模块 (2)后端 (3)前端 二、开发模式 一、实战学习的引言 (1)前后端的大致学习模块 (2)后端 Validation:做参数校验Mybatis:做数据库的操作Redis:做缓存Junit:单元测试项目部署:springboot项目部署相关的知识 (3)前端 Vite:Vue项目的脚手架Router:路由Pina:状态管理Eleme

状态模式state

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/state 在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。 在状态模式中,player.getState()获取的是player的当前状态,通常是一个实现了状态接口的对象。 onPlay()是状态模式中定义的一个方法,不同状态下(例如“正在播放”、“暂停