面试:Spring中单例模式用的是哪种?

面试中被问到设计模式的概率还是蛮高的,尤其是问:你在项目中用过设计模式吗?

面对这个问题,我也在做模拟面试时问过很多人,大部分都会回答Spring中的单例模式。但是只要追问:单例模式有很多种写法,那Spring中用的是哪一种呢?于是很多朋友一脸懵。

单例模式

单例模式是一种常用的设计模式,用于确保一个类只有一个实例,并提供一个全局访问点。

在实现单例模式时,常见的几种写法包括:

  1. 饿汉式(Eager Initialization):

    • 优点:线程安全,实现简单,不需要考虑多线程同步问题。

    • 缺点:在类加载时就创建实例,可能会造成资源浪费。

  2. 懒汉式(Lazy Initialization):

    • 优点:延迟加载,只有在第一次使用时才会创建实例。

    • 缺点:线程不安全,需要考虑多线程同步问题。

  3. 双重检查锁定(Double-Checked Locking):

    • 优点:延迟加载,线程安全。

    • 缺点:实现较为复杂。

  4. 静态内部类(Static Inner Class):

    • 优点:延迟加载,线程安全,实现简单。

    • 缺点:无法传递参数给构造函数。

  5. 枚举(Enum):

    • 优点:线程安全,实现简单,可以防止反射和序列化攻击。

    • 缺点:无法延迟加载。

每种写法都有其优点和缺点,选择适合的写法取决于具体的需求和场景。

到底用哪些模式?

如果对线程安全要求较高,可以选择饿汉式或双重检查锁定;如果对延迟加载要求较高,可以选择懒汉式或静态内部类;如果需要防止反射和序列化攻击,可以选择枚举实现单例模式。

spring 单例模式

Spring框架提供了一种单例模式的实现方式,即通过IoC容器管理Bean的生命周期来实现单例模式。

在Spring中,通过在配置文件或者注解中声明Bean的作用域为singleton,就可以将该Bean定义为单例模式。当容器初始化时,会创建该Bean的一个实例,并将其放入容器中。之后,每次请求该Bean时,都会返回同一个实例。

Spring的单例模式实现原理主要有以下几个步骤:

  1. 容器初始化:当Spring容器启动时,会读取配置文件或者注解,解析Bean的定义信息,并创建Bean的实例。

  2. 创建单例Bean:当容器创建Bean的实例时,会根据Bean的作用域来判断是否需要创建单例Bean。如果Bean的作用域为singleton,则容器会创建一个单例Bean的实例,并将其放入容器中。

  3. 容器管理单例Bean:容器会将创建的单例Bean实例放入一个缓存中,以便后续的请求可以直接返回该实例。

  4. 返回单例Bean:每次请求该单例Bean时,容器会直接从缓存中获取该实例,并返回给调用方。

需要注意的是,Spring的单例模式是基于容器的,即容器负责管理Bean的生命周期和实例化过程。因此,开发人员无需手动管理单例对象的创建和销毁,只需要通过容器来获取单例Bean的实例即可。

下面是一个使用Spring注解方式实现单例模式的示例:

复制代码
@Component
@Scope("singleton")
public class SingletonBean {
    // 单例Bean的属性和方法
}

在上述示例中,通过@Component注解将该类声明为一个Bean,并使用@Scope("singleton")注解将其作用域定义为singleton,从而实现了单例模式。

Spring Bean单例模式的设计

Spring Bean采用了双重校验锁以及ConcurrentHashMap作为容器实现了单例设计,并且通过三级缓存解决循环依赖的问题。我们来看下Spring Bean的创建方法,在AbstractBeanFactory类中。

复制代码
 protected <T> T doGetBean(
   String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
   throws BeansException {
  String beanName = transformedBeanName(name);
  Object beanInstance;
  //先判断容器中是否存在
  Object sharedInstance = getSingleton(beanName);
  if (sharedInstance != null && args == null) {
   //...省略
   beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
  }
  else {
   //...省略 判断BeanDefinition 是否存在...
   try {
    if (requiredType != null) {
     beanCreation.tag("beanType", requiredType::toString);
    }
    RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
    checkMergedBeanDefinition(mbd, beanName, args);
    //...省略
    }
    //进行Bean的实例化
    if (mbd.isSingleton()) {
     //调用DefaultSingletonBeanRegistry的getSingleton方法,使用lambda表达式
     sharedInstance = getSingleton(beanName, () -> {
      try {
       return createBean(beanName, mbd, args);
      }
      catch (BeansException ex) {
      }
     });
    }
    //...省略
 }

可以看到在创建Bean之前会先去判断容器中是否存在Bean对象,存在的话直接获取,代码如下:

复制代码
    //一级缓存
 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

 //二级缓存
 private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
 
 //三级缓存
 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

 //获取单例Bean 一级缓存 -> 二级缓存 -> 三级缓存
 protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    singletonObject = this.earlySingletonObjects.get(beanName);
    if (singletonObject == null && allowEarlyReference) {
     synchronized (this.singletonObjects) { //同步锁,解决Bean存在于三级缓存HashMap中的线程安全问题
      singletonObject = this.singletonObjects.get(beanName);
      if (singletonObject == null) {
       singletonObject = this.earlySingletonObjects.get(beanName);
       if (singletonObject == null) {
        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
         singletonObject = singletonFactory.getObject();
         this.earlySingletonObjects.put(beanName, singletonObject);
         this.singletonFactories.remove(beanName);
        }
       }
      }
     }
    }
   }
   return singletonObject;
}

当三级缓存中都不存在相应的Bean对象时,则进行Bean对象的创建,调用DefaultSingletonBeanRegistry的getSingleton方法:

复制代码
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
  Assert.notNull(beanName, "Bean name must not be null");
  synchronized (this.singletonObjects) { //同步锁
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null) {
    if (this.singletonsCurrentlyInDestruction) {
     //...省略
    }
    //...省略
    boolean newSingleton = false;
    boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
    if (recordSuppressedExceptions) {
      //...省略
    }
    try {
     //singletonFactory为函数式接口,由上面可知此方法会去创建Bean 会调用createBean方法
     singletonObject = singletonFactory.getObject();
     newSingleton = true;
    }
    catch (IllegalStateException ex) {
     //...省略
    }
    catch (BeanCreationException ex) {
     //...省略
    }
    finally {
     //...省略
    }
    if (newSingleton) {
     //添加单例Bean进入容器中
     addSingleton(beanName, singletonObject);
    }
   }
   return singletonObject;
  }
}

addSingleton()方法:

复制代码
protected void addSingleton(String beanName, Object singletonObject) {
   synchronized (this.singletonObjects) { //同步锁
   this.singletonObjects.put(beanName, singletonObject); //添加对象进入一级缓存
   this.singletonFactories.remove(beanName); //移除三级缓存对象
   this.earlySingletonObjects.remove(beanName); //移除二级缓存对象
   this.registeredSingletons.add(beanName);
 }
}

可以看出在Spring中是通过类似双重校验锁方式并配合ConcurrentHashMap这个线程安全的HashMap,来完成Bean的单例创建,使得默认生成的Bean在容器中有且仅有一个,也保证了在创建过程中内存有且仅有一个对象

在线刷题小程序

再聊几句

文章前面提到面试官问你在项目中有没有用过什么设计模式,Spring中的单例模式是人家实现Bean单例而使用的单例模式,面试官更多的是想问你在项目中某个业务场景中用到过什么设计模式。

所以,在面试之前,建议你想想之前做过的项目中用过什么什么设计模式。

推荐准备:

  • 单例模式

  • 策略模式

  • 模板方法

  • 装饰器模式

这四种设计模式相对来说,在项目中运用场景比较多,通用性相对比较强。

好了,今天就分享这么多。

相关推荐
Lee川13 小时前
优雅进化的JavaScript:从ES6+新特性看现代前端开发范式
javascript·面试
Lee川16 小时前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i18 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有19 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有19 小时前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫20 小时前
Looper.loop() 循环机制
面试
AAA梅狸猫20 小时前
Handler基本概念
面试
Wect20 小时前
浏览器缓存机制
前端·面试·浏览器
掘金安东尼21 小时前
Fun with TypeScript Generics:玩转 TS 泛型
前端·javascript·面试