`

【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. }  
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定义如下(一定要用注解的注入方式,才会有可能发生死锁!):

 

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. }  
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代码 复制代码 收藏代码
  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>  
<?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.       尽量在容器初始化完,开始对外服务。

分享到:
评论

相关推荐

    面试真题包含spring-java-集合-框架-并发-spring-运维-数据库等多领域45卷合集.rar

    Spring框架是Java开发中的核心部分,"Spring基础篇.pdf"和"Spring高级篇二.pdf"将涵盖IoC容器、AOP、Spring MVC、Spring Boot、Spring Data JPA等核心概念。求职者需要理解依赖注入和面向切面编程的原理,以及如何...

    架构师学习线路图,详细介绍java学习线路图,包括java多线程并发,JVM,spring,springboot,s.zip

    学习Spring包括了解IoC容器、Bean生命周期管理、Spring MVC、数据访问支持(如JDBC、Hibernate、MyBatis)以及Spring Boot的自动配置等。 4. **SpringBoot**:SpringBoot简化了Spring应用的初始搭建和配置工作,它...

    java面试指南包括 Java基础、Java并发、JVM、MySQL、Redis、Spring、MyBatis、Kafka等

    面试者需要了解Spring的IoC容器,AOP的实现原理,以及Spring Boot简化开发的方式。 七、MyBatis MyBatis是一个轻量级的持久层框架,它将SQL与Java代码解耦。面试者应掌握MyBatis的映射文件配置、动态SQL、结果映射...

    整理了一份大厂常考面试题,这份pdf包括 Java基础、Java并发、JVM、MySQL、Redis、Spring、

    理解IoC容器的工作原理,以及Spring MVC的请求处理流程,是必备技能。 MyBatis作为轻量级持久层框架,面试时会考察动态SQL、映射文件配置、事务管理等方面的知识。 Kafka作为消息队列,面试中会涉及它的消息模型、...

    java 后端学习资料包含(spring,多线程).zip

    本资料包中可能包含有关Spring框架的源码解析,帮助开发者理解其内部工作原理,例如,IoC容器是如何初始化和管理bean的,AOP是如何实现的,以及Spring MVC的请求处理流程。关于多线程部分,可能涵盖理论知识、实战...

    2024 Java面试宝典合集

    4. **并发容器和线程池**:在多线程环境下,Java的并发容器(如ConcurrentHashMap、BlockingQueue)和线程池(ExecutorService、ThreadPoolExecutor、ScheduledExecutorService)至关重要。面试题可能涉及线程安全、...

    java面试题java面试题

    7. **Java并发编程**:Java并发编程最全面试题可能包含并发安全问题、死锁、活锁、饥饿等问题,以及并发控制机制如synchronized、volatile、ThreadLocal等。熟悉并发容器(如ConcurrentHashMap)和并发工具(如...

    互联网高频Java后端面试题20道(适合1~3年)V1.0.70.docx

    除此之外,面试中还可能涉及其他主题,如设计模式(单例、工厂、观察者等)、数据库查询优化(索引、JOIN操作)、并发编程(线程池、死锁)、网络协议(HTTP/HTTPS)、Spring Boot、Spring Cloud、Docker、...

    java部分面试题答案.7z

    并发编程专题可能涉及线程安全、死锁、活锁、饥饿、线程间通信(wait/notify、条件变量)以及Java并发工具类的使用。 2. **RabbitMQ**:这是一个开源的消息代理和队列服务器,广泛用于实现消息中间件。面试中可能会...

    2011 广州 面试笔试题:.doc

    10. Spring的控制反转(IOC)使得对象的创建和依赖关系的管理交给容器,降低了组件间的耦合。 11. Spring、Struts和Hibernate在项目中分别负责视图跳转、数据持久化和业务逻辑管理。 12. Spring的API包括AOP和IoC,...

    Java相关的技术文档保存

    - IoC(Inversion of Control)容器:理解如何通过XML或注解配置实现对象的创建和依赖管理。 - AOP(Aspect-Oriented Programming):了解切面、通知、切入点等概念,以及如何实现面向切面的编程。 - Spring MVC...

    高级JAVA面试资料总结

    在IT行业中,Java作为一种广泛应用的编程语言,其高级面试知识点涵盖了广泛的领域,包括但不限于核心语法、数据结构与算法、多线程、并发编程、JVM内存管理、设计模式、Spring框架、微服务、数据库优化以及分布式...

    java程序员面试大纲错过了金三银四你还要错过2018吗.pdf,这是一份不错的文件

    4. **数据结构与算法**:面试可能包含对数组内存分配的理解,以及Java中各种数据结构(如HashMap、LinkedList)的应用,比如在并发环境下的问题及解决方案,如ConcurrentHashMap和线程安全的容器。 5. **Spring框架...

    Guns 文档高级版

    文档会深入讲解Spring的核心组件,如IoC容器、Spring MVC、Spring Boot和Spring Data等。 5. **数据库交互**:在Java应用中,数据库操作是必不可少的部分。文档会涵盖JDBC基础、ORM(对象关系映射)框架如Hibernate...

    练习:每日练习和常用知识点练习-并发编程,JVM,剑指提供,LeetCode,spring

    理解Spring的核心概念,如IoC容器、Bean的生命周期、AOP的实现原理,以及Spring Boot和Spring Cloud在微服务架构中的应用,对于构建复杂的企业级应用至关重要。 通过`practice-master`这个压缩包文件,你将有机会...

    Java后端体系高级面试题

    - Spring IoC容器,依赖注入 - AOP(面向切面编程):切点,通知,代理 - Spring Boot与Spring Cloud的应用与配置 10. **分布式系统**: - 分布式缓存:Redis,Memcached - 分布式锁:Zookeeper,RedLock - ...

    JAVA面试题

    面试中可能会问到Spring的IoC容器如何工作,Bean的生命周期,以及如何配置和使用AOP。此外,Spring MVC作为Spring的Web模块,对于控制器、模型、视图和适配器的理解也是考察的重点。 Struts2是一个基于MVC设计模式...

    阿里云面试经验,详细记录所有面试题,分享个人面试心得

    * Spring IOC的概念和优点 五、中间件篇 * Dubbo的调用链路和负载均衡策略 * Dubbo Provider的配置方式和并发请求上限控制 * 消息中间件产品的优缺点和比较 * Spring Cloud熔断机制的介绍 六、数据库篇 * 锁机制...

    JavaEE企业级分布式高级架构师培训.zip

    9. **并发编程**:在高并发环境下,如何设计并行系统,避免死锁和线程安全问题是重要课题。这部分可能涵盖线程池、并发容器、锁机制等。 10. **安全性**:包括HTTPS、JWT、OAuth2.0等,以及Spring Security或Apache...

    互联网高频Java后端面试题20道(适合1~3年)V1.0.30.docx

    以上仅列举了部分面试题及其解析,涵盖的 Java 后端知识点包括 RESTful API 设计、Spring IoC 容器、微服务架构、ORM 框架、JVM 内存管理、并发编程、异常处理、集合框架、SQL 注入防护以及安全框架等。这些知识点...

Global site tag (gtag.js) - Google Analytics