`
singleant
  • 浏览: 375065 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

【Spring】IOC容器并发条件下,可能发生死锁

阅读更多

 

 1.背景

 

上周在生产环境应用启动时,发生应用频频发生死锁的现象。原因是因为 spring IOC 容器还未初始化完成,就有工作线程调用 context.getBean() 来获取容器里的对象。具体产生死锁的原因条件有:

1.       应用启动的时候 Main 线程进行 spring 容器初始化。

2.       容器初始化的过程中有工作线程也起来了并开始工作。

3.       工作线程代码里显式调用 spring ioc 容器的 context.getBean(String beanName)

4.       工作线程显式获取的 bean 未实例化,且里存在直接或者间接的注解注入方式的情况。

 

以上情况都符合,那工作线程和 main 线程可能发生死锁。

 

2.具体原因分析

Spring ioc 容器组合里有两个重要的 map

   /** Map of bean definition objects, keyed by bean name */

   private final Map beanDefinitionMap = CollectionFactory.createConcurrentMapIfPossible (16);

//bean definition spring 容器里描述 bean 对象的元数据( bean 的创建等就是基于此来创建)。 Spring 容器初始化实例之前需要先把配置文件的 bean 定义都转化成内部的统一描述对象 BeanDefinition 。该 beanDefinitionMap 用于保存这些数据。

   /** Cache of singleton objects: bean name --> bean instance */

   private final Map singletonObjects = CollectionFactory.createConcurrentMapIfPossible (16);

//spring 容器用于 cache spring 容器初始化的单例对象

以上两个对象为了保证数据的一致性,在操作的时候很多时候会进行加锁。如以下两个过程。

 

 

过程一: spring 容器初始化

Spring 容器初始化的时候会实例化所有单例对象( preInstantiateSingletons ),这个过程中会对上面两个对象加锁,以防止并发。先对 beanDefinitionMap 加锁,防止元数据被修改,然后在每次实例化单例对象的时候对 singletonObjects 加锁,防止并发修改。

过程二:根据 spring 容器获取一个单例对象。

调用 spring 容器的 context.getBean beanName ),如果该 bean 是单例且还未实例化,这个时候就需要进行实例化,如果该 bean 直接或间接存在注解方式的 bean 注入的时候,过程中也会对以上两个对象进行加锁防止并发。先对 singleObjects 加锁,从改 map 里找是否有存在 beanName 的对象,没有的话在创建该 bean 的过程中会对 beanDefinitionMap 加锁。

 

可以看出以上过程一和过程二对两个对象的锁顺序是不一致的,所以并发执行就可能会发生死锁。

 

在本机写了一个简单的实验,死锁的线程栈信息可以证明这一点,具体如下:



 

 

代码十分简单,如下:

package com.alibaba.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class DeadLockTest {

    ClassPathXmlApplicationContext context = null;

    public void startContext() {
        context = new ClassPathXmlApplicationContext();
        context.setConfigLocation("spring/bean/mybeans.xml");
        context.refresh();
    }

    public void getBean() {
        new Thread(new GetBean()).start();
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        DeadLockTest t = new DeadLockTest();
        //get bean 工作线程
        t.getBean();
        //容器启动主线程
        t.startContext();
    }
     //get bean 工作线程
    class GetBean implements Runnable {

        public void run() {
            try {
                Thread.sleep(10000);
                MyBean myBean = (MyBean) context.getBean("myBean");
                myBean.getOtherBean().sayHello();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            // TODO Auto-generated method stub

        }

    }

}

 

其中mybean定义如下(一定要用注解的注入方式,才会有可能发生死锁!):

 

package com.alibaba.test;

import org.springframework.beans.factory.annotation.Autowired;

public class MyBean {
    @Autowired
    private OtherBean otherBean;

    public OtherBean getOtherBean() {
        return otherBean;
    }

    public void setOtherBean(OtherBean otherBean) {
        this.otherBean = otherBean;
    }

    public void sayHello() {
        otherBean.sayHello();
    }
}

 

myBean.xml:

<?xml version="1.0" encoding="GB2312"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans default-autowire="byName">
	<bean
		class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
		
	<bean id="myBean" class="com.alibaba.test.MyBean" />

</beans>

 

以上代码经过调试控制,即会发生思索,控制如下:

1.main线程在DefaultListableBeanFactory.preInstantiateSingletons 方法的

synchronized (this.beanDefinitionMap) {

...

}里加个断点

2.getBean工作线程在DefaultSingletonBeanRegistry.getSingleton(String beanName, ObjectFactory singletonFactory)方法的

synchronized (this.singletonObjects) {

...

}里加个断点。

3.等1,2断点都进去之后,再触发继续运行,就会发生死锁。

 

结论也许可以算是 spring bug ,也许可以算我们使用不当。

 

总之,有两点需要注意:

1.       尽量避免显式调用 ioc 容器,注入工作由容器自己来完成。

2.       尽量在容器初始化完,开始对外服务。

  • 大小: 661 KB
2
2
分享到:
评论
2 楼 lixia0417 2015-12-22  
楼主你这个是版本的问题吧。我3.2.3的版本并没有同时持有两个锁的情况发生啊.都是先释放了一个以后才获取另一个锁的
1 楼 bao231 2011-09-23  
感觉我们程序里有很多这样的场景啊,呵呵

如果在spring的bean初始话init参数配置的afterPropertiesSet这个方法里另外开启一个线程
这样也可能发生死锁了?

class beaniNit inplenmens  InitializingBean,BeanFactoryAware{

beanFactory=null;

afterPropertiesSet(){

beanFactory.getBean("myBean").start();///这个地方如果新开线程取的话,也可能发生死锁!!
}

setBeanFactory(beanFactory ){

this.beanFactory= beanFactory;

}


}

相关推荐

Global site tag (gtag.js) - Google Analytics