19 spring是如何保证bean是单例的?

vvEcho 2025-02-27 17:39:39
Categories: Tags:

spring是通过三级缓存与双重检测锁的协作来保证bean是单例的

一级缓存(singletonObjects):存储完全初始化后的单例 Bean 实例。
二级缓存(earlySingletonObjects):存储提前暴露的 Bean 引用(未完成属性填充和初始化)。
三级缓存(singletonFactories):存储 Bean 工厂对象(ObjectFactory),用于解决循环依赖时提前生成代理对象

关键源码流程(参考 DefaultSingletonBeanRegistry.getSingleton()):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

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

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 第一次检测:从一级缓存快速获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
synchronized (this.singletonObjects) {
// 第二次检测:加锁后再次检查
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 通过工厂创建早期引用(可能触发代理生成)
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
}

延伸问题: 为什么1级缓存是线程安全的容器,而二三级缓存是非线程的容器

首先,1级缓存存储完整单例对象的容器,是全局共享的,所有线程都可能并发访问,必须保证高并发下的数据一致性;所以采用了CHM锁机制,保证线程安全。
1级缓存的创建是为了支持高并发的读取,而不是创建;bean的创建是串行化,并不存在并发创建的场景;

Bean 的创建过程通过同步机制保证线程安全,而 CHM 则优化了初始化后的并发访问性能
其次,二级缓存是存储早期暴露的 Bean 引用(未完成属性注入和初始化),主要用于解决循环依赖问题(如 A 依赖 B,B 又依赖 A)
虽然使用 HashMap,但其访问仅在同步代码块内进行(例如 getSingleton() 方法中的 synchronized 锁),通过锁机制保证线程安全。
另外二级缓存的生命周期较短,仅在 Bean 创建过程中临时使用,且通过同步块控制并发,无需额外线程安全容器的开销

最后,三级缓存存储 ObjectFactory 工厂对象,用于生成 Bean 的早期引用(可能包含代理对象),解决循环依赖和 AOP 代理的动态生成问题
三级缓存的写入和读取操作集中在单一线程的 Bean 创建流程中(如 createBean() 方法),不存在并发场景,因此无需线程安全容器。
工厂对象(ObjectFactory)仅被调用一次(生成早期引用后即移除),生命周期极短,无需复杂同步机制,所以也是普通hashMap