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

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

相关文章

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

SpringBoot如何通过Map实现策略模式

《SpringBoot如何通过Map实现策略模式》策略模式是一种行为设计模式,它允许在运行时选择算法的行为,在Spring框架中,我们可以利用@Resource注解和Map集合来优雅地实现策略模式,这... 目录前言底层机制解析Spring的集合类型自动装配@Resource注解的行为实现原理使用直接使用M

C#原型模式之如何通过克隆对象来优化创建过程

《C#原型模式之如何通过克隆对象来优化创建过程》原型模式是一种创建型设计模式,通过克隆现有对象来创建新对象,避免重复的创建成本和复杂的初始化过程,它适用于对象创建过程复杂、需要大量相似对象或避免重复初... 目录什么是原型模式?原型模式的工作原理C#中如何实现原型模式?1. 定义原型接口2. 实现原型接口3

大数据spark3.5安装部署之local模式详解

《大数据spark3.5安装部署之local模式详解》本文介绍了如何在本地模式下安装和配置Spark,并展示了如何使用SparkShell进行基本的数据处理操作,同时,还介绍了如何通过Spark-su... 目录下载上传解压配置jdk解压配置环境变量启动查看交互操作命令行提交应用spark,一个数据处理框架

Java实现状态模式的示例代码

《Java实现状态模式的示例代码》状态模式是一种行为型设计模式,允许对象根据其内部状态改变行为,本文主要介绍了Java实现状态模式的示例代码,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来... 目录一、简介1、定义2、状态模式的结构二、Java实现案例1、电灯开关状态案例2、番茄工作法状态案例

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的功能,即该应用程序