一则spring容器启动死锁问题(DefaultListableBeanFactory)

线上发现一个问题,应用在启动时会卡死,log上并没有什么异常输出,初判应该是死锁问题.
抓现场的thread dump文件, 确实是有两个线程有deadlock问题.

线程一

"HSFBizProcessor-8-thread-13" daemon prio=10 tid=0x00007fc686a83000 nid=0x37128 waiting for monitor entry [0x000000004b7f3000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinitionNames(DefaultListableBeanFactory.java:192)
    - waiting to lock <0x00000007707d84c8> (a java.util.concurrent.ConcurrentHashMap)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanNamesForType(DefaultListableBeanFactory.java:209)
    at org.springframework.beans.factory.BeanFactoryUtils.beanNamesForTypeIncludingAncestors(BeanFactoryUtils.java:187)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:652)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:610)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:412)
    at org.springframework.beans.factory.annotation.InjectionMetadata.injectFields(InjectionMetadata.java:105)
    at 

线程二

"main":
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:180)
    - waiting to lock <0x00000007707ae6c0> (a java.util.concurrent.ConcurrentHashMap)
    at org.springframework.beans.factory.support.AbstractBeanFactory.isFactoryBean(AbstractBeanFactory.java:747)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:422)
    - locked <0x00000007707d84c8> (a java.util.concurrent.ConcurrentHashMap)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:728)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:380)
    - locked <0x00000007707d7fc8> (a java.lang.Object)
    at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:255)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:199)

栈文件的结尾已经指出了两个线程在竞争什么锁,

  which is held by "main"
"main":
  waiting to lock monitor 0x00007fc681220a08 (object 0x00000007707ae6c0, a java.util.concurrent.ConcurrentHashMap),
  which is held by "HSFBizProcessor-8-thread-13"
"HSFBizProcessor-8-thread-13":
  waiting to lock monitor 0x00007fc686692438 (object 0x00000007707d84c8, a java.util.concurrent.ConcurrentHashMap),
  which is held by "main"

主要是线程HSFBizProcessor的DefaultListableBeanFactory.getBeanDefinitionNames(DefaultListableBeanFactory.java:192)
需要锁对象0x00000007707d84c8, 而这个对象已经被main的DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:422)
锁住了.

spring的版本是2.5.

这个问题的原因是spring在初始化singleton bean的时候,需要锁两个地方,第一个是
DefaultSingletonBeanRegistry中的singletonObjects:

/** Cache of singleton objects: bean name --> bean instance */
    private final Map singletonObjects = CollectionFactory.createConcurrentMapIfPossible(16);
        ...
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                ...
                try {
                    singletonObject = singletonFactory.getObject();
                }

singletonObjects是DefaultSingletonBeanRegistry中的并发Map.

第二步需要锁DefaultListableBeanFactory中的beanDefinitionMap:

/** Map of bean definition objects, keyed by bean name */
private final Map beanDefinitionMap = CollectionFactory.createConcurrentMapIfPossible(16);
    ...
    public String[] getBeanDefinitionNames() {
        synchronized (this.beanDefinitionMap) {
            if (this.frozenBeanDefinitionNames != null) {
                return this.frozenBeanDefinitionNames;
            }
            ...
        }
    }

如果在应用启动时只有一个线程进入spring初始化bean时是没问题的, 但这里应用代码在spring的容器启动的同时,有另外一个main方法同时开始运行调用spring的DefaultListableBeanFactory.preInstantiateSingletons方法,两个线程两把锁,是有可能造成饥饿竞争的.

在spring容器外自行调用spring的创建bean方法要注意线程问题.

引申阅读:
Spring Bean Creation is Not Thread Safe

A Java Thread deadlock has occured

Performance bottleneck and potential thread deadlock in DefaultSingletonBeanRegistry

Spring deadlocks between DefaultListableBeanFactory and DefaultSingletonBeanRegistry


麦芽面包
杭州程序员乱弹,聊技术,看世界。兴趣方向互联网分布式系统稳定性建设,容量规划,压测,监控,容灾多...

科幻影迷,书虫,硬核玩家,译者

1k 声望
1.5k 粉丝
0 条评论
推荐阅读
Street coder 1.4.1 -1.4.2
我们持续对于最佳技术的追求源自银弹的谬论。我们认为有一种技术能成倍提升我们的生产力。其实并没有。比如,Python是一种解释语言。你不需要编译Python代码-它可以立即运行。更好的是,你不用指定变量的类型,能...

祝坤荣阅读 311

我服了!SpringBoot升级后这服务我一个星期都没跑起来!(上)
最近由于各方面的原因在准备升级 Spring Cloud 和 Spring Boot,经过一系列前置的调研和分析,决定把Spring Boot 相关版本从 2.1.6 升级到 2.7.5,Spring Cloud 相关版本从 Greenwich.SR1 升级为 2021.0.4。

艾小仙2阅读 800

从源码层面深度剖析Spring循环依赖
作者:郭艳红以下举例皆针对单例模式讨论图解参考 [链接]1、Spring 如何创建Bean?对于单例Bean来说,在Spring容器整个生命周期内,有且只有一个对象。Spring 在创建 Bean 过程中,使用到了三级缓存,即 Default...

京东云开发者3阅读 565

封面图
SpringMVC-ResponseBodyAdvice
ResponseBodyAdvice接口可以在将handler方法的返回值写入response前对返回值进行处理,例如将返回值封装成一个与客户端约定好的对象以便于客户端处理响应数据。本篇文章将学习如果使用ResponseBodyAdvice以及其实...

半夏之沫2阅读 4.3k

Mybatis缓存机制
Mybatis内置了强大的事务性查询缓存机制,正确使用Mybatis的缓存机制可以有效提高应用的性能。因为一般情况下我们应用的大部分性能消耗都和数据库查询有关,如果能够有效命中缓存、适当避免或减少与数据库的交互...

2阅读 943

SpringMVC-@InitBinder
由@InitBinder注解修饰的方法用于初始化WebDataBinder对象,能够实现:从request获取到handler方法中由@RequestParam注解或@PathVariable注解修饰的参数后,假如获取到的参数类型与handler方法上的参数类型不匹配...

半夏之沫1阅读 2.8k

手把手带你开发starter,点对点带你讲解原理
现在我们就来回忆一下,在还没有Spring-boot框架的时候,我们使用Spring 开发项目,如果需要某一个框架,例如mybatis,我们的步骤一般都是:

京东云开发者2阅读 485

封面图

科幻影迷,书虫,硬核玩家,译者

1k 声望
1.5k 粉丝
宣传栏