当前位置: 首页 > Java > 正文

Spring 3.1.4之前版本中一个deadlock bug

1 星2 星3 星4 星5 星 (2 次投票, 评分: 5.00, 总分: 5)
Loading ... Loading ...
baidu_share

有个应用碰到重启后没响应的问题,于是jstack -l dump下线程,看看都在做什么,jstack -l执行后,看到最后有提示有deadlock,这两个造成deadlock的线程的堆栈如下:
线程A:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)

线程B:

1
2
3
4
5
6
7
8
9
10
11
12
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)

可以看到线程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():

1
2
3
synchronized (this.beanDefinitionMap) {
beanNames = new ArrayList(this.beanDefinitionNames);
}

相当于不再一直锁着beanDefinitionMap,而是cp出当前的beanDefinitionNames来遍历,这样就避免了deadlock问题,同时也提高了性能。

本文固定链接: http://www.chepoo.com/spring-3-1-4-previous-deadlock-bug.html | IT技术精华网

Spring 3.1.4之前版本中一个deadlock bug:等您坐沙发呢!

发表评论