关于spring三级缓存的解读
作者:ambity_lyf
spring三级缓存的解读
spring 中为了解决 B的重复利用,A 依赖B 的循环依赖,aop 问题,多线程可能拿到不完整的bean 的问题引入了3层缓存,分别是
/** Cache of singleton objects: bean name to bean instance. */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of early singleton objects: bean name to bean instance. */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); /** Cache of singleton factories: bean name to ObjectFactory. */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
singletonObjects用于存放单例bean 实例earlySingletonObjects用于存放早期的bean 实例singletonFactories用于存放简单工厂实例
从3级缓存中 获取bean 的方法有如下两个
1.从缓存中获取,主要用来解决循环依赖问题
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 这里不用考虑指令重排,因为 bean 初始化完成后才会放入map
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 加锁,与另一个getSingleton 方法 互斥访问,保证并发情况下获取到不完整的bean
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 获取工厂bean ,因为可能是aop 生成的代理,这里二级和三级缓存保证获取到的bean 是最后的完整bean
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 放入 二级缓存中,从3级缓存中移除
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}2.创建bean 的真正逻辑
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
// 再次从一级缓存中获取,因为多线程加载同一个bean 时,
//相当于 双重检查
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
// 标记bean 正在创建
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}我们先假设不存在aop 与多线程竞争的情况,此时我们只需要一级缓存就可以解决循环依赖的问题
- 1.构造bean A ->
- 2.把A 放入 map1 ->
- 3. 填充A的属性 ->
- 4.创建bean B ->
- 5.填充B->
- 6.从缓存中拿到A->
- 7.返回B ->
- 8返回A
即不存在aop且不考虑多线程的情况下,只要一个map1 我们就能解决循环依赖的问题
3.现在我们考虑aop
假设A 有属性B ,B 有属性A ,且我们对A 做了aop,在创建B 的时候我们希望拿到的是A 的aop 代理对象,此时假设填充B 的时候获取A ,
假设如果我们用一层缓存解决?
可以对是否aop 代理过存储一个标志位,此时必然在后面获取bean 的时候都要多一层判断,所以此时需要二层缓存来解决
一层存放aop 代理过的对象或不需要aop 的对象,一层存放非aop 对象,即创建工厂。
4.我们再考虑并发的情况,即多线程获取一个单例
这里如果熟悉单例模式的创建过程,就不难理解第二个方法中的synchronized 与加锁后再次尝试从一级缓存中获取,把第一个方法可以看作第一次非空判断,那么就是双重锁检查保证单例。但是只做了双重锁检查保证单例可以支持多线程操作吗?
答案是否定的,假设我们为了解决aop 使用了二层缓存,对于A 来说 并没有完全完成赋值和初始化的操作,但是由于A 依赖B ,B依赖于A的aop 代理对象,此时A 已经在一级中了,那么线程2边能从map1 中获取到 对象A,(当然你也可以对整个创建过程加锁,那么就不存在多线程问题)。但是此时A 对象并没有完成赋值与init 的方法执行,这样很容易造成线程2 的异常,所以还需要一层缓存将完成整个创建过程的bean ,与没有创建完成的纯洁bean 隔离开,所以才有了3级缓存。
此时再考虑3级缓存的获取,基于并发的考虑就不难理解第一个方法为何要synchronized 了,即保证并发下,缓存只在一层缓存中存在。
综上来说,我的理解,aop 问题需要两层缓存来解决,而考虑多线程并发的情况下,所以需要三层缓存来解决。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
