相信大家碰到源码时经常无从下手🙃,不知道从哪开始阅读,面对大量代码晕头转向,索性就读不下去了,又浪费了一次提升自己的机会😭。
我认为有一种方法,可以解决大家的困扰!那就是通过阅读某一次开源的【PR】,从这个入口出发去阅读源码!!
至此,我们发现自己开始从大量堆砌的源码中脱离开来😀,柳暗花明又一村。
一、前瞻
Ok,开始我们今天的PR阅读。
我们看下PR的标题,翻译过来是在单例创建期间进行同步可能会导致死锁。
通过这个标题我们就可以思考本次的阅读线索了,看起来可以学到不少东西:
- 旧代码的死锁是怎么产生的
- 贡献者通过改变什么来解决死锁呢
二、探索
Ok,我们来整体看下PR的所有提交。
代码涉及修改了Bean创建工厂、Spring IOC容器的上下文,猜测是在bean创建过程进行修复。
而且大家注意下提交下面这行提交注释,Introduce background bootstrapping for individual singleton beans
为单个单例引入后台引导。
看下在ConfigurableBeanFactory类中确实引入了一个线程,那我们就来看看引入的这个线程具体做了什么工作。
@Nullable
private CompletableFuture<?> preInstantiateSingleton(String beanName, RootBeanDefinition mbd) {
if (mbd.isBackgroundInit()) {
Executor executor = getBootstrapExecutor(); // 获取Executor
if (executor != null) {
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
getBean(dep);
}
}
CompletableFuture<?> future = CompletableFuture.runAsync(
() -> instantiateSingletonInBackgroundThread(beanName), executor);
addSingletonFactory(beanName, () -> {
try {
future.join();
}
catch (CompletionException ex) {
ReflectionUtils.rethrowRuntimeException(ex.getCause());
}
return future; // not to be exposed, just to lead to ClassCastException in case of mismatch
});
return (!mbd.isLazyInit() ? future : null);
}
else if (logger.isInfoEnabled()) {
logger.info("Bean '" + beanName + "' marked for background initialization " +
"without bootstrap executor configured - falling back to mainline initialization");
}
}
if (!mbd.isLazyInit()) {
instantiateSingleton(beanName);
}
return null;
}
可以看到这段核心代码通过getBootstrapExecutor获取该线程后,再通过该Executor线程去执行了instantiateSingletonInBackgroundThread
方法。
private void instantiateSingletonInBackgroundThread(String beanName) {
this.preInstantiationThread.set(PreInstantiation.BACKGROUND);
try {
instantiateSingleton(beanName);
}
catch (RuntimeException | Error ex) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to instantiate singleton bean '" + beanName + "' in background thread", ex);
}
throw ex;
}
finally {
this.preInstantiationThread.set(null);
}
}
private void instantiateSingleton(String beanName) {
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
if (bean instanceof SmartFactoryBean<?> smartFactoryBean && smartFactoryBean.isEagerInit()) {
getBean(beanName);
}
}
else {
getBean(beanName);
}
}
instantiateSingletonInBackgroundThread
方法的作用也就是在通过该Executor线程在后台初始化该Bean,也就是说初始化Bean不使用主线程来创建而是通过创建另一个后台线程来去初始化Bean,也对应了本次PR的提交注释:
Introduce background bootstrapping for individual singleton beans
为单个单例引入后台引导
那为什么要创建后台线程来初始化Bean,而不使用主线程呢?
我们翻阅下PR提交的讨论。
大致意思就是Micrometer对象会窃听GC通知,所以它会等待单例创建锁,而主线程拥有单例创建锁。
如果我们使用主线程去创建Micrometer单例的话,Micrometer的创建完成需要主线程释放锁,而主线程释放锁又需要Micrometer先完成创建。
这就无限循环了,最终导致了死锁。
到这里就解决我们的阅读线索1了,大家还记得不?
阅读线索1:旧代码的死锁是怎么产生的
而阅读线索2的答案也显而易见,就是上文提到的通过后台线程来创建Micrometer单例。
阅读线索2:贡献者通过改变什么来解决死锁呢
主线程通过后台线程创建Micrometer单例,因为是异步执行不需要等待创建完成就可以释放锁,而后台线程等待到主线程的单例锁后就可以继续执行流程,避免了死锁的发生。
未完待续。。。
好了,今天的分享就到这🤔。大家能否感受到通过PR这种方式来阅读源码的乐趣呢 !
创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️