说明
阅读dubbo官方文档,看到初始化死锁部分,特地在此记录一番,并结合线上业务给出可能的解决方案。
死锁产生的原因
当spring解析到<dubbo:serivce />时,就已经向外暴露了服务,而spring还在初始化其他的bean,此时如果有消费者的请求进来,并且服务的提供者类中含有调用applicationContext.getBean()的用法时,那么会产生死锁。
请求线程的applicationContext.getBean()方法,会先同步singletonObjects判断bean是否存在,不存在则同步beanDefinitionMap进行初始化,并再次同步singletonObjects写入该bean;
而对于spring初始化线程来说,是不需要判断bean是否存在的,直接同步beanDefinitionMap进行初始化,并同步singletonObjects写入该bean;
此时就发生了死锁:请求线程先锁singletonObjects,再锁beanDefinitionMap,最后再锁singleObjects;而spring初始化线程先锁beanDefinitionMap,再锁singleObjects,反向锁导致线程死锁(即当需要加多把锁,但是加锁的顺序不一致时,会产生死锁),此时启动失败,无法提供服务。
(扩展:springIOC容器中有两个重要的map–beanDefinitionMap和singletonObjects,beanDefinitionMap中保存的是对bean的描述信息,即会将基于xml或者注解配置的bean转换成内部统一描述对象BeanDefinition进行存储,在创建bean时会对该对象加锁,防止篡改元数据信息;singletonObjects保存的单例对象的集合,在实例化对象时会对该对象进行加锁,防止并发修改)
规避办法
- 尽量不要使用applicationContext进行注入,而是采用spring IOC进行注入;毕竟不能IOC才是spring的精华,不能使spring沦落为工厂模式~
- 如果一定要用getBean方法,可以将dubbo配置放在最后,保证spring最后加载dubbo配置;
- 如果不想依赖配置之间的顺序,可以配置dubbo进行延迟加载,即<dubbo: provider delay="-1" />,目的使dubbo在spring完成初始化之后再对外暴露服务;
- 如果大量使用getBean,那么spring已经作为工厂模式在使用,可以将dubbo服务进行隔离单独的spring服务;