tags spring 死锁
转载传送门:http://singleant.iteye.com/blog/1159823
在项目中遇到了spring启动死锁的问题,参考singleant的分析演示
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. 尽量在容器初始化完,开始对外服务。