当前位置:首页 > 科技  > 软件

警惕!SpringBoot错误发布事件,造成死锁Deadlock

来源: 责编: 时间:2024-06-12 08:43:40 82观看
导读环境:SpringBoot3.2.51. 死锁复现1.1 自定义事件监听public class PackApplicationEvent extends ApplicationEvent { private String message ; public PackApplicationEvent(String message, Object source) {

环境:SpringBoot3.2.5iCO28资讯网——每日最新资讯28at.com

1. 死锁复现

1.1 自定义事件监听

public class PackApplicationEvent extends ApplicationEvent {  private String message ;  public PackApplicationEvent(String message, Object source) {    super(source) ;    this.message = message ;  }  public String getMessage() {    return message ;  }}

自定义事件,接收消息及相关数据iCO28资讯网——每日最新资讯28at.com

1.2 自定义事件监听

@Componentpublic class PackApplicationListener implements ApplicationListener<PackApplicationEvent> {  @Override  public void onApplicationEvent(PackApplicationEvent event) {    System.out.printf("接收到事件消息: %s, 数据: %s%n", event.getMessage(), event.getSource().toString()) ;    // TODO  }}

该事件监听器只打印了信息。iCO28资讯网——每日最新资讯28at.com

1.3 发布事件

@Componentpublic class EventProcessor {  public EventProcessor(ApplicationEventPublisher eventPublisher) {    Thread t = new Thread(() -> {      eventPublisher.publishEvent(new PackApplicationEvent("自定义事件", EventProcessor.this));    });    t.start() ;    try {      System.out.println("线程启动,等待执行完成...") ;      t.join() ;    } catch (InterruptedException e) {      System.err.printf("线程中断: %s, 错误: %s%n", Thread.currentThread().getName(), e.getMessage()) ;    }  }}

该Bean在构造函数中新启一个线程发布事件,同时通过join方法等待线程执行完成。iCO28资讯网——每日最新资讯28at.com

上面的程序运行后,发现输出了上面的打印内容后应用没有继续运行。打印整个线程栈(通过jstack命令查看),如下:iCO28资讯网——每日最新资讯28at.com

图片图片iCO28资讯网——每日最新资讯28at.com

根据线程信息,main线程在创建EventProcessor对象时,会先持有DefaultSingletonBeanRegistry.singletonObjects这个ConcurrentHashMap对象锁接着创建EventProcessor对象实例,在调用该对象的构造函数时,启动新的线程Thread-1,该线程发布事件同时通过join方法等待T1这个线程完成,在发布事件时Spring容器会获取所有的ApplicationListener,此时就会又创建PackApplicationListener对象,创建该对象同样要获取singletonObjects锁对象,这样就造成了死锁。iCO28资讯网——每日最新资讯28at.com

主线程

图片图片iCO28资讯网——每日最新资讯28at.com

主线程创建EventProcessor对象。iCO28资讯网——每日最新资讯28at.com

Thread-1线程

图片图片iCO28资讯网——每日最新资讯28at.com

Thread-1线程获取容器中的ApplicationListener类型的bean,该过程将执行到如下步骤:iCO28资讯网——每日最新资讯28at.com

图片图片iCO28资讯网——每日最新资讯28at.com

main线程持有singletonObjects锁,Thread-1线程又期望获取到该锁,但是main线程还要等待Thread-1线程执行完成。这死锁了。iCO28资讯网——每日最新资讯28at.com

以上是对死锁的复现及原因进行了分析,接下来进行问题的解决。iCO28资讯网——每日最新资讯28at.com

2. 解决问题

2.1 解决方式1

不要在构造函数中发布事件,而是应该在所有的单例对象都创建完后再执行,也就是实现SmartInitializingSingleton接口,该接口对应的回调方法会在所有的单例bean都创建完以后执行,这样就不会再出现deadlock问题。iCO28资讯网——每日最新资讯28at.com

@Componentpublic class EventProcessor implements SmartInitializingSingleton {  private final ApplicationEventPublisher eventPublisher ;  public EventProcessor(ApplicationEventPublisher eventPublisher) {    this.eventPublisher = eventPublisher ;  }  @Override  public void afterSingletonsInstantiated() {    Thread t = new Thread(() -> {      eventPublisher.publishEvent(new PackApplicationEvent("自定义事件", EventProcessor.this));    });    t.start() ;    try {      t.join() ;    } catch (InterruptedException e) {      System.err.printf("线程中断: %s, 错误: %s%n", Thread.currentThread().getName(), e.getMessage()) ;    }  }}

这样改造后容器能正常的启动,同时事件也正常的发布&监听。iCO28资讯网——每日最新资讯28at.com

afterSingletonsInstantiated方法的调用在如下:iCO28资讯网——每日最新资讯28at.com

public class DefaultListableBeanFactory {  public void preInstantiateSingletons() {    for (String beanName : beanNames) {      // 创建单例bean      getBean(beanName);    }    // 单例bean创建完成以后,执行afterSingletonsInstantiated回调方法    for (String beanName : beanNames) {      Object singletonInstance = getSingleton(beanName);      if (singletonInstance instanceof SmartInitializingSingleton smartSingleton) {        smartSingleton.afterSingletonsInstantiated();      }    }  }}

以上就不会在出现锁问题。iCO28资讯网——每日最新资讯28at.com

2.2 解决方式2

升级Spring版本到Spring6.2(目前并没有正式发布),你仍然可以使用6.2.0-SNAPSHOT版本,该版本通过多线程方式初始化Bean对象,这样就不会出现deadlock问题。iCO28资讯网——每日最新资讯28at.com


iCO28资讯网——每日最新资讯28at.com

本文链接://www.dmpip.com//www.dmpip.com/showinfo-26-93206-0.html警惕!SpringBoot错误发布事件,造成死锁Deadlock

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 聊聊 Mybatis 动态 SQL

下一篇: 网易面试:SpringBoot如何开启虚拟线程?

标签:
  • 热门焦点
Top
Baidu
map