记一次程序死锁问题排查

本文分析了一个Spring Cloud应用启动时出现的死锁情况,通过jstack命令发现主线程与消费者线程之间形成了循环等待资源的死锁状态。问题源于在声明消费者Bean时直接调用了start()方法,导致在Spring Context未加载完成时启动消费。解决方案是在容器启动完成后调用start(),确保避免在初始化阶段获取Bean。
摘要由CSDN通过智能技术生成

死锁四个条件

  1. 互斥条件:一个资源每次只能被一个线程使用
  2. 占有且等待:一个线程因请求资源而阻塞时,对已获得的资源保持不放
  3. 不可强行占有:线程已获得的资源,在未使用完时,不能强行剥夺
  4. 循环等待条件:若干线程之间形成一种头尾相连的循环等待资源关系

问题排查

程序在启动过程中出现卡死现象,程序启动日志卡住,消费者消费到消息日志也是卡住的,凭感觉应该是程序启动的时候出现问题导致主线程挂起,于是执行下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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值