DBMNG数据库管理与应用

科学是实事求是的学问,来不得半点虚假。
当前位置:首页 > 经验分享 > Java组件

【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 加锁。

 

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

 

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



 

 

代码十分简单,如下:

Java代码  收藏代码
  1. package com.alibaba.test;  
  2.   
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;  
  4.   
  5. public class DeadLockTest {  
  6.   
  7.     ClassPathXmlApplicationContext context = null;  
  8.   
  9.     public void startContext() {  
  10.         context = new ClassPathXmlApplicationContext();  
  11.         context.setConfigLocation("spring/bean/mybeans.xml");  
  12.         context.refresh();  
  13.     }  
  14.   
  15.     public void getBean() {  
  16.         new Thread(new GetBean()).start();  
  17.     }  
  18.   
  19.     /** 
  20.      * @param args 
  21.      */  
  22.     public static void main(String[] args) {  
  23.         DeadLockTest t = new DeadLockTest();  
  24.         //get bean 工作线程  
  25.         t.getBean();  
  26.         //容器启动主线程  
  27.         t.startContext();  
  28.     }  
  29.      //get bean 工作线程  
  30.     class GetBean implements Runnable {  
  31.   
  32.         public void run() {  
  33.             try {  
  34.                 Thread.sleep(10000);  
  35.                 MyBean myBean = (MyBean) context.getBean("myBean");  
  36.                 myBean.getOtherBean().sayHello();  
  37.             } catch (InterruptedException e) {  
  38.                 // TODO Auto-generated catch block  
  39.                 e.printStackTrace();  
  40.             }  
  41.             // TODO Auto-generated method stub  
  42.   
  43.         }  
  44.   
  45.     }  
  46.   
  47. }  

 

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

 

Java代码  收藏代码
  1. package com.alibaba.test;  
  2.   
  3. import org.springframework.beans.factory.annotation.Autowired;  
  4.   
  5. public class MyBean {  
  6.     @Autowired  
  7.     private OtherBean otherBean;  
  8.   
  9.     public OtherBean getOtherBean() {  
  10.         return otherBean;  
  11.     }  
  12.   
  13.     public void setOtherBean(OtherBean otherBean) {  
  14.         this.otherBean = otherBean;  
  15.     }  
  16.   
  17.     public void sayHello() {  
  18.         otherBean.sayHello();  
  19.     }  
  20. }  

 

myBean.xml:

Xml代码  收藏代码
  1. <?xml version="1.0" encoding="GB2312"?>  
  2. <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">  
  3. <beans default-autowire="byName">  
  4.     <bean  
  5.         class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />  
  6.           
  7.     <bean id="myBean" class="com.alibaba.test.MyBean" />  
  8.   
  9. </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.       尽量在容器初始化完,开始对外服务。

本站文章内容,部分来自于互联网,若侵犯了您的权益,请致邮件chuanghui423#sohu.com(请将#换为@)联系,我们会尽快核实后删除。
Copyright © 2006-2023 DBMNG.COM All Rights Reserved. Powered by DEVSOARTECH            豫ICP备11002312号-2

豫公网安备 41010502002439号