关于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 问题需要两层缓存来解决,而考虑多线程并发的情况下,所以需要三层缓存来解决。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。