【Spring源码】单例创建期间进行同步可能会导致死锁?

相信大家碰到源码时经常无从下手🙃,不知道从哪开始阅读,面对大量代码晕头转向,索性就读不下去了,又浪费了一次提升自己的机会😭。

我认为有一种方法 ,可以解决大家的困扰!那就是通过阅读某一次开源的【PR】,从这个入口出发去阅读源码!!

至此,我们发现自己开始从大量堆砌的源码中脱离开来😀,柳暗花明又一村。

一、前瞻

Ok,开始我们今天的PR阅读

我们看下PR的标题,翻译过来是在单例创建期间进行同步可能会导致死锁

通过这个标题我们就可以思考本次的阅读线索了,看起来可以学到不少东西:

  1. 旧代码的死锁是怎么产生的
  2. 贡献者通过改变什么来解决死锁呢

二、探索

Ok,我们来整体看下PR的所有提交。

代码涉及修改了Bean创建工厂、Spring IOC容器的上下文,猜测是在bean创建过程进行修复。

而且大家注意下提交下面这行提交注释Introduce background bootstrapping for individual singleton beans为单个单例引入后台引导。

看下在ConfigurableBeanFactory类中确实引入了一个线程,那我们就来看看引入的这个线程具体做了什么工作。

java 复制代码
	@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方法。

java 复制代码
	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这种方式来阅读源码的乐趣呢

创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️

相关推荐
廋到被风吹走6 小时前
【Spring】Spring Data JPA Repository 自动实现机制深度解析
java·后端·spring
MX_93596 小时前
Spring中Bean的配置(一)
java·后端·spring
sg_knight10 小时前
Spring 框架中的 SseEmitter 使用详解
java·spring boot·后端·spring·spring cloud·sse·sseemitter
喵个咪13 小时前
初学者入门:用 go-kratos-admin + protoc-gen-typescript-http 快速搭建企业级 Admin 系统
后端·typescript·go
用户214118326360214 小时前
手把手教你用Claude制作专属PPT生成器-从模板学习到自动生成全流程实战
后端
计算机毕设匠心工作室16 小时前
【python大数据毕设实战】全面皮肤病症状数据可视化分析系统、Hadoop、计算机毕业设计、包括数据爬取、数据分析、数据可视化、机器学习、实战教学
后端·python·mysql
摆烂工程师16 小时前
2025年12月最新的 Google AI One Pro 1年会员教育认证通关指南
前端·后端·ai编程
qq_124987075317 小时前
基于SpringBoot+vue的小黄蜂外卖平台(源码+论文+部署+安装)
java·开发语言·vue.js·spring boot·后端·mysql·毕业设计
代码与野兽17 小时前
AI交易,怎么让LLM自己挑选数据源?
前端·javascript·后端
天天摸鱼的java工程师17 小时前
JDK 25 到底更新了什么?这篇全景式解读带你全面掌握
java·后端