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

简介: 通过这个标题我们就可以思考本次的阅读线索了,看起来可以学到不少东西。1. 旧代码的死锁是怎么产生的。2. 贡献者通过改变什么来解决本次PR的问题呢?而阅读线索2的答案也显而易见,就是上文提到的通过后台线程来创建Micrometer单例...

在这里插入图片描述

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


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


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

一、前瞻

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

在这里插入图片描述

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

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

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

二、探索

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

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

相关文章
|
1月前
|
XML 缓存 Java
Spring源码之 Bean 的循环依赖
循环依赖是 Spring 中经典问题之一,那么到底什么是循环依赖?简单说就是对象之间相互引用, 如下图所示: 代码层面上很好理解,在 bean 创建过程中 class A 和 class B 又经历了怎样的过程呢? 可以看出形成了一个闭环,如果想解决这个问题,那么在属性填充时要保证不二次创建 A对象 的步骤,也就是必须保证从容器中能够直接获取到 B。 一、复现循环依赖问题 Spring 中默认允许循环依赖的存在,但在 Spring Boot 2.6.x 版本开始默认禁用了循环依赖 1. 基于xml复现循环依赖 定义实体 Bean java复制代码public class A {
|
2月前
|
监控 数据可视化 关系型数据库
微服务架构+Java+Spring Cloud +UniApp +MySql智慧工地系统源码
项目管理:项目名称、施工单位名称、项目地址、项目地址、总造价、总面积、施工准可证、开工日期、计划竣工日期、项目状态等。
307 6
|
2月前
|
Java 关系型数据库 数据库连接
Spring源码解析--深入Spring事务原理
本文将带领大家领略Spring事务的风采,Spring事务是我们在日常开发中经常会遇到的,也是各种大小面试中的高频题,希望通过本文,能让大家对Spring事务有个深入的了解,无论开发还是面试,都不会让Spring事务成为拦路虎。
35 1
|
1月前
|
Java 测试技术 数据库连接
【Spring源码解读!底层原理高级进阶】【下】探寻Spring内部:BeanFactory和ApplicationContext实现原理揭秘✨
【Spring源码解读!底层原理高级进阶】【下】探寻Spring内部:BeanFactory和ApplicationContext实现原理揭秘✨
|
2天前
|
XML 人工智能 Java
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
Spring Bean名称生成规则(含源码解析、自定义Spring Bean名称方式)
|
10天前
|
Java 关系型数据库 MySQL
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
UWB (ULTRA WIDE BAND, UWB) 技术是一种无线载波通讯技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。一套UWB精确定位系统,最高定位精度可达10cm,具有高精度,高动态,高容量,低功耗的应用。
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
|
1月前
|
Java Spring
使用spring实现邮件的发送(含测试,源码,注释)
使用spring实现邮件的发送(含测试,源码,注释)
7 0
|
1月前
|
XML Java 开发者
【Spring源码解读 底层原理高级进阶】【上】探寻Spring内部:BeanFactory和ApplicationContext实现原理讲解
【Spring源码解读 底层原理高级进阶】【上】探寻Spring内部:BeanFactory和ApplicationContext实现原理讲解
|
缓存 Java Spring
Spring 获取单例流程(三)
读完这篇文章你将会收获到 • Spring 何时将 bean 加入到第三级缓存和第一级缓存中 • Spring 何时回调各种 Aware 接口、BeanPostProcessor 、InitializingBean 等
107 0
|
缓存 Java Spring
Spring 获取单例流程(二)
读完这篇文章你将会收获到 • Spring 中 prototype 类型的 bean 如何做循环依赖检测 • Spring 中 singleton 类型的 bean 如何做循环依赖检测
65 0