死锁四个条件
- 互斥条件:一个资源每次只能被一个线程使用
- 占有且等待:一个线程因请求资源而阻塞时,对已获得的资源保持不放
- 不可强行占有:线程已获得的资源,在未使用完时,不能强行剥夺
- 循环等待条件:若干线程之间形成一种头尾相连的循环等待资源关系
问题排查
程序在启动过程中出现卡死现象,程序启动日志卡住,消费者消费到消息日志也是卡住的,凭感觉应该是程序启动的时候出现问题导致主线程挂起,于是执行下jstack命令查看下线程快照,得到下面的结果
Found one Java-level deadlock:
=============================
"ConsumeMessageThread_18":
waiting to lock monitor 0x000000002299e428 (object 0x00000006c4a374f0, a java.lang.String),
which is held by "ConsumeMessageThread_8"
"ConsumeMessageThread_8":
waiting to lock monitor 0x000000002baa6188 (object 0x00000006c3ab1578, a java.util.concurrent.ConcurrentHashMap),
which is held by "main"
"main":
waiting to lock monitor 0x000000002299e428 (object 0x00000006c4a374f0, a java.lang.String),
which is held by "ConsumeMessageThread_8"
看来之前的猜想是正确的,由于出现了死锁所以出现程序日志出现卡住的情况,但是是什么原因导致死锁呢?接着查看死锁线程的堆栈信息
"main":
at org.springframework.cloud.context.scope.GenericScope$BeanLifecycleWrapper.getBean(GenericScope.java:388)
- waiting to lock <0x00000006c4a374f0> (a java.lang.String)
at org.springframework.cloud.context.scope.GenericScope.get(GenericScope.java:186)
- locked <0x00000006c3ab1578> (a java.util.concurrent.ConcurrentHashMap)
"ConsumeMessageThread_8":
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:216)
- waiting to lock <0x00000006c3ab1578> (a java.util.concurrent.ConcurrentHashMap)
- locked <0x00000006c4a374f0> (a java.lang.String)
at org.springframework.cloud.context.scope.GenericScope.get(GenericScope.java:186)
可以看到主线程持有0x00000006c3ab1578并等待获取0x00000006c4a374f0在org.springframework.cloud.context.scope.GenericScope$BeanLifecycleWrapper.getBean这个位置;消费者线程8等待获取0x00000006c3ab1578并持有0x00000006c4a374f0在DefaultSingletonBeanRegistry.getSingleton这个位置;这样就形成了一个死锁。
有上面的信息可以大致得出主线程spring context未加载完成时,有消费线程需要从spring context中获取bean,从而导致死锁。正常的来说在spring context未加载完成时,rocketMQ消费线程应该不会工作的。问题原因只能着手去业务代码中发现问题了
@Bean
public DefaultMQPushConsumer defaultMQPushConsumer() {
.......
.......
//通过依赖注入的Fegin调用
consumer.start();
}
在声明消费者Bean的时候直接调用了start()方法,这样就会导致Bean初始化的时候就会启动消费。
快速解决方案
public class Test implements ApplicationRunner {
@Autowired
DefaultMQPushConsumer consumer;
@Override
public void run(ApplicationArguments args) throws Exception {
consumer.start();
}
}
在容器启动完成之后再调用start()方法,这样就避免了spring context还未加载完成就去获取Bean。