Spring容器初始化死锁的问题

发现

今天对系统修改了一些代码,在日常、预发都正常,但是发布到线上的时候,部署10多分钟都没有启动应用,aone日志一直显示tomcat start ,直到超时。登录到服务器上查看日志,翻遍各种日志没有发现异常报错。注释掉notify部分的功能,启动正常。

排查

notify初始化问题?

仔细查看了tomcat_stdout.log,发现日志停留在notify初始化之后,难道是notify初始化异常了?

notify.png

查看/home/admin/logs/notify/notify_client.log日志文件,没有报错

用命令查看下notify的连接情况

连接也一切正常

依赖包版本冲突?

输入命令查看依赖树

把notify下面的依赖冲突全部解决了一遍,启动问题依然存在

死锁了?

在https://ops.alibaba-inc.com/page/appManage.html#appManage 找到相关的应用,对有问题的机器执行jstack操作,然后在http://zprofiler.alibaba-inc.com/thread/index.htm 中分析

image

果然有死锁,罪魁祸首终于找到了,那我们看看是怎么死锁了

image

image

线程msgWorkTP-1734460873-1-thread-8 锁定了 0x0000000771ae7518 等待 0x0000000771aaab08

线程Catalina-startStop-1 锁住了 0x0000000771aaab08 等待 0x0000000771ae7518

分析

死锁的出现在DefaultListableBeanFactory.getBeanDefinitionNames()、DefaultSingletonBeanRegistry.getSingleton()和DefaultListableBeanFactory.preInstantiateSingletons(),那我们就看看具体实现。

image

image

image

image

线程Catalina-startStop-1初始化singleton bean时,需要获取beanDefinitionMap的锁,并在判断是否为FactoryBean的时候需要通过getSingleton()获取实体,在获取实体的时候获取singletonObjects的锁,以保证singleton的bean只初始化一次。

线程msgWorkTP-1734460873-1-thread-8是notify初始化完成,接收到消息,需要获得bean进行请求处理,通过getBean()也调用了getSingleton(),此时也需要获取sigletonObjects的锁,再层层调用到了DefaultListableBeanFactory.getBeanDefinitionNames(),这时候又需要beanDefinitionMap的锁。

所以线程Catalina-startStop-1在初始化的时候先锁定了beanDefinitionMap,在等待singletonObjects的锁,但是这个时候msgWorkTP-1734460873-1-thread-8已经获得了sigletonObjects的锁,等待beanDefinitionMap锁,这样就导致了死锁

解决

1、Spring 3.1.4之前的版本都存在并发死锁的问题,可以采用了升级spring的方案解决

2、可以在容器初始化之后,在对notify的消息进行处理

总结

出现这个问题是因为,我的应用在notify初始化之后,接收消息进行业务处理,这个时候容器还没有初始化完成而导致的死锁,日常和预发没有出现问题是因为没有消息推送。

之前没有遇到此类问题,导致在排查问题的时候走了弯路,在没有任何日志的情况下,没有立马想到可能是死锁问题。

此条目发表在Java分类目录。将固定链接加入收藏夹。

发表评论