環境: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問題。
本文鏈接:http://www.www897cc.com/showinfo-26-93206-0.html警惕!SpringBoot錯誤發布事件,造成死鎖Deadlock
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
上一篇: 聊聊 Mybatis 動態 SQL