Spring 3.1.4之前版本中一个deadlock bug

有个应用碰到重启后没响应的问题,于是jstack -l dump下线程,看看都在做什么,jstack -l执行后,看到最后有提示有deadlock,这两个造成deadlock的线程的堆栈如下:
线程A:
[code]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinitionNames(DefaultListableBeanFactory.java:192)
– waiting to lock <0x000000077151f4a0> (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 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessAfterInstantiation(AutowiredAnnotationBeanPostProcessor.java:240)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:959)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:472)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory$1.run(AbstractAutowireCapableBeanFactory.java:409)
at java.security.AccessController.doPrivileged(Native Method)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:380)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:264)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
– locked <0x000000076d022290> (a java.util.concurrent.ConcurrentHashMap)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:261)
[/code]
线程B:
[code]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:181)
– waiting to lock <0x000000076d022290> (a java.util.concurrent.ConcurrentHashMap)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:166)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:206)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:185)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:164)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:429)
– locked <0x000000077151f4a0> (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 <0x000000076e0f2250> (a java.lang.Object)
at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:255)
[/code]
可以看到线程A持有0x000000076d022290,在等0x000000077151f4a0,而线程B持有0x000000077151f4a0,在等0x000000076d022290,于是就deadlock了。

有意思的是deadlock的相关代码是Spring的,这个应用用到的Spring版本为2.5.6SEC02,翻了下代码,可以很容易看到这两段代码逻辑确实会造成deadlock。

从Spring的行为上来说,在这个场景中之所以会产生deadlock。
原因为B线程正在将所有singleton的spring bean进行初始化,在做这步动作时,需要synchronized (this.beanDefinitionMap),此后会遍历beanDefinitionMap所有的bean,对其中singleton的bean进行初始化,在进行初始化时,会需要synchronized (this.singletonObjects),以确保这些singleton的bean只会创建一次。

A线程此时的一个spring bean正在处理请求,对这个spring bean有配置aop的处理,当调用这个spring bean的某方法时会经过aop增强执行另外一个spring bean的一些动作,因此先通过DefaultSingletonBeanRegistry.getSingleton获取这个singleton的bean,此时会先synchronized (this.singletonObjects),然后逻辑一路会执行到DefaultListableBeanFactory.getBeanDefinitionNames,此时又必须synchronized (this.beanDefinitionMap),于是就死锁了。

因此按照这样的逻辑,其实只要是两个spring bean,假设一个是A,另外一个aop增强配置的spring bean B,在创建spring ApplicationContext时,如A已完成初始化,但B还未完成,此时又有请求A已经在处理,那么就会碰到上面的deadlock。

这个bug在Spring官方的jira上也有:https://jira.springsource.org/browse/SPR-7718
在3.1.4的版本中fixed,因此如果是此前的版本都有可能会不幸的碰到这个问题,解决方法一种是升级版本,另一种方法则是绕开这个bug,例如上面的A、B两个bean,可以在bean A上配置depends-on=”B”,强制让B在A之前完成初始化,这样也没问题。
3.1.4中的修复方法为修改了DefaultListableBeanFactory.preInstantiateSingletons():
synchronized (this.beanDefinitionMap) {
beanNames = new ArrayList(this.beanDefinitionNames);
}
相当于不再一直锁着beanDefinitionMap,而是cp出当前的beanDefinitionNames来遍历,这样就避免了deadlock问题,同时也提高了性能。

=============================
欢迎关注微信公众号:hellojavacases

关于此微信号:
分享Java问题排查的Case、Java业界的动态和新技术、Java的一些小知识点Test,以及和大家一起讨论一些Java问题或场景,这里只有Java细节的分享,没有大道理、大架构和大框架。

公众号上发布的消息都存放在http://hellojava.info上。

《Spring 3.1.4之前版本中一个deadlock bug》有3个想法

  1. 英雄!!!! 你救了我一命。
    我用的spring3.1.2 也是莫名其妙的挂在这个synchronized 里了~
    public String[] getBeanDefinitionNames() {
    synchronized (this.beanDefinitionMap) {
    if (this.frozenBeanDefinitionNames != null) {
    return this.frozenBeanDefinitionNames;
    }
    else {
    return StringUtils.toStringArray(this.beanDefinitionNames);
    }
    }
    }

发表评论

电子邮件地址不会被公开。 必填项已用*标注


*