环境:SpringBoot3.2.5
public class PackApplicationEvent extends ApplicationEvent { private String message ; public PackApplicationEvent(String message, Object source) { super(source) ; this.message = message ; } public String getMessage() { return message ; }}
自定义事件,接收消息及相关数据
@Componentpublic class PackApplicationListener implements ApplicationListener<PackApplicationEvent> { @Override public void onApplicationEvent(PackApplicationEvent event) { System.out.printf("接收到事件消息: %s, 数据: %s%n", event.getMessage(), event.getSource().toString()) ; // TODO }}
该事件监听器只打印了信息。
@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方法等待线程执行完成。
上面的程序运行后,发现输出了上面的打印内容后应用没有继续运行。打印整个线程栈(通过jstack命令查看),如下:
图片
根据线程信息,main线程在创建EventProcessor对象时,会先持有DefaultSingletonBeanRegistry.singletonObjects这个ConcurrentHashMap对象锁接着创建EventProcessor对象实例,在调用该对象的构造函数时,启动新的线程Thread-1,该线程发布事件同时通过join方法等待T1这个线程完成,在发布事件时Spring容器会获取所有的ApplicationListener,此时就会又创建PackApplicationListener对象,创建该对象同样要获取singletonObjects锁对象,这样就造成了死锁。
图片
主线程创建EventProcessor对象。
图片
Thread-1线程获取容器中的ApplicationListener类型的bean,该过程将执行到如下步骤:
图片
main线程持有singletonObjects锁,Thread-1线程又期望获取到该锁,但是main线程还要等待Thread-1线程执行完成。这死锁了。
以上是对死锁的复现及原因进行了分析,接下来进行问题的解决。
不要在构造函数中发布事件,而是应该在所有的单例对象都创建完后再执行,也就是实现SmartInitializingSingleton接口,该接口对应的回调方法会在所有的单例bean都创建完以后执行,这样就不会再出现deadlock问题。
@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()) ; } }}
这样改造后容器能正常的启动,同时事件也正常的发布&监听。
afterSingletonsInstantiated方法的调用在如下:
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(); } } }}
以上就不会在出现锁问题。
升级Spring版本到Spring6.2(目前并没有正式发布),你仍然可以使用6.2.0-SNAPSHOT版本,该版本通过多线程方式初始化Bean对象,这样就不会出现deadlock问题。
本文链接://www.dmpip.com//www.dmpip.com/showinfo-26-93206-0.html警惕!SpringBoot错误发布事件,造成死锁Deadlock
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 聊聊 Mybatis 动态 SQL