欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring之ShutDown?Hook死鎖現(xiàn)象解讀

 更新時間:2023年04月04日 09:46:12   作者:Epoch-Elysian  
這篇文章主要介紹了Spring之ShutDown?Hook死鎖現(xiàn)象解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

Spring ShutDown Hook死鎖現(xiàn)象

偶然出現(xiàn)一次項目異常spring卻沒有正常停止的情況,最終發(fā)現(xiàn)是Spring Shutdown導(dǎo)致的死鎖現(xiàn)象。

某個框架里嵌入了類似這樣的一段代碼

@Component
public class ShutDownHookTest implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (onException) {
		    System.out.println("test shutdown hook deadlock");
			System.exit(0);
		}
 
    }
}

它的邏輯就是想要在出現(xiàn)異常后,通過System.exit來確保應(yīng)用程序退出。

而且沒有使用異步事件,是在主線程下跑了System.exit,然后就發(fā)現(xiàn)springboot server還是正常運行著的。

而且程序看著好像也沒問題,由于我們是dubbo服務(wù)化系統(tǒng),在測試環(huán)境上服務(wù)還是正常的。

這很明顯不符常理,正常來說,System.exit這樣的指令是spring能夠感知到的,并且會執(zhí)行shutDown處理的,先來看看Spring 注冊ShutdownHook

public abstract class AbstractApplicationContext extends DefaultResourceLoader
		implements ConfigurableApplicationContext {
	@Override
	public void registerShutdownHook() {
		if (this.shutdownHook == null) {
			// No shutdown hook registered yet.
			this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) {
				@Override
				public void run() {
				    //重點在這里獲取startupShutdownMonitor的監(jiān)視器鎖
					synchronized (startupShutdownMonitor) {
						doClose();
					}
				}
			};
			Runtime.getRuntime().addShutdownHook(this.shutdownHook);
		}
	}
 
	protected void doClose() {
		// Check whether an actual close attempt is necessary...
		if (this.active.get() && this.closed.compareAndSet(false, true)) {
			if (logger.isDebugEnabled()) {
				logger.debug("Closing " + this);
			}
 
			if (!NativeDetector.inNativeImage()) {
				LiveBeansView.unregisterApplicationContext(this);
			}
 
			try {
				// Publish shutdown event.
				publishEvent(new ContextClosedEvent(this));
			}
			catch (Throwable ex) {
				logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
			}
 
			// Stop all Lifecycle beans, to avoid delays during individual destruction.
			if (this.lifecycleProcessor != null) {
				try {
					this.lifecycleProcessor.onClose();
				}
				catch (Throwable ex) {
					logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
				}
			}
 
			// Destroy all cached singletons in the context's BeanFactory.
			destroyBeans();
 
			// Close the state of this context itself.
			closeBeanFactory();
 
			// Let subclasses do some final clean-up if they wish...
			onClose();
 
			// Reset local application listeners to pre-refresh state.
			if (this.earlyApplicationListeners != null) {
				this.applicationListeners.clear();
				this.applicationListeners.addAll(this.earlyApplicationListeners);
			}
 
			// Switch to inactive.
			this.active.set(false);
		}
	}
}	

也就是說spring新起了一個線程,加入了JVM Shutdown鉤子函數(shù)。

重點是close前要獲取startupShutdownMonitor的對象監(jiān)視器鎖,這個鎖看著就很眼熟,Spring在refresh時也會獲取這把鎖。

public abstract class AbstractApplicationContext extends DefaultResourceLoader
		implements ConfigurableApplicationContext {
 
	@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
 
			// Prepare this context for refreshing.
			prepareRefresh();
 
			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
 
			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);
 
			......
		}
	}
}	

這個時候我們猜想,是獲取startupShutdownMonitor死鎖了。

jstack打下線程??纯?/h2>

 "SpringContextShutdownHook" #18 prio=5 os_prio=0 tid=0x0000000024e00800 nid=0x407c waiting for monitor entry [0x000000002921f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at org.springframework.context.support.AbstractApplicationContext$1.run(AbstractApplicationContext.java:991)
        - waiting to lock <0x00000006c494f430> (a java.lang.Object)

"main" #1 prio=5 os_prio=0 tid=0x0000000002de4000 nid=0x1ff4 in Object.wait() [0x0000000002dde000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000006c4a43118> (a org.springframework.context.support.AbstractApplicationContext$1)
        at java.lang.Thread.join(Thread.java:1252)
        - locked <0x00000006c4a43118> (a org.springframework.context.support.AbstractApplicationContext$1)
        at java.lang.Thread.join(Thread.java:1326)
        at java.lang.ApplicationShutdownHooks.runHooks(ApplicationShutdownHooks.java:107)
        at java.lang.ApplicationShutdownHooks$1.run(ApplicationShutdownHooks.java:46)
        at java.lang.Shutdown.runHooks(Shutdown.java:123)
        at java.lang.Shutdown.sequence(Shutdown.java:167)
        at java.lang.Shutdown.exit(Shutdown.java:212)
        - locked <0x00000006c4845128> (a java.lang.Class for java.lang.Shutdown)
        at java.lang.Runtime.exit(Runtime.java:109)
        at java.lang.System.exit(System.java:971)
        at io.seata.server.ShutDownHookTest.onApplicationEvent(ShutDownHookTest.java:12)
        at io.seata.server.ShutDownHookTest.onApplicationEvent(ShutDownHookTest.java:7)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169)
        at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:421)
        at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:378)
        at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:938)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586)
        - locked <0x00000006c494f430> (a java.lang.Object)
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:144)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:771)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:763)

乍一看jstack并沒有提示線程死鎖(jvisualvm、jconsle之類的工具也不行),但是從線程棧來看:

  • main線程先獲取到了startupShutdownMonitor鎖 <0x00000006c494f430>
  • SpringContextShutdownHook線程在等待startupShutdownMonitor鎖
  • main線程掉了Thread.join阻塞在獲取<0x00000006c4a43118>這把鎖

根本原因是main線程調(diào)System.exit阻塞住了,一直往下追蹤,會發(fā)現(xiàn)阻塞在ApplicationShutdownHooks這里

class ApplicationShutdownHooks {
    /* Iterates over all application hooks creating a new thread for each
     * to run in. Hooks are run concurrently and this method waits for
     * them to finish.
     */
    static void runHooks() {
        Collection<Thread> threads;
        synchronized(ApplicationShutdownHooks.class) {
            threads = hooks.keySet();
            hooks = null;
        }
 
        for (Thread hook : threads) {
            hook.start();
        }
        for (Thread hook : threads) {
            while (true) {
                try {
					// 等待shutdow線程結(jié)束
                    hook.join();
                    break;
                } catch (InterruptedException ignored) {
                }
            }
        }
    }
}

總結(jié)

整個死鎖的流程:

  • main線程-spring refresh開始時會獲取startupShutdownMonitor對象監(jiān)視器鎖
  • main線程-在spring refresh還未完成的時候,觸發(fā)了System.exit指令
  • SpringContextShutdownHook線程-SpringContextShutdownHook線程開始工作,等待獲取startupShutdownMonitor對象監(jiān)視器鎖
  • main線程調(diào)用Thread.join等待SpringContextShutdownHook線程結(jié)束

所以在Spring未完成refresh時,是不能夠觸發(fā)System.exit指令的

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Spring Boot REST國際化的實現(xiàn)代碼

    Spring Boot REST國際化的實現(xiàn)代碼

    本文我們將討論如何在現(xiàn)有的Spring Boot項目中添加國際化。只需幾個簡單的步驟即可實現(xiàn)Spring Boot應(yīng)用的國際化,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-10-10
  • Java動態(tài)代理實現(xiàn)_動力節(jié)點Java學院整理

    Java動態(tài)代理實現(xiàn)_動力節(jié)點Java學院整理

    動態(tài)代理作為代理模式的一種擴展形式,廣泛應(yīng)用于框架(尤其是基于AOP的框架)的設(shè)計與開發(fā),本文將通過實例來講解Java動態(tài)代理的實現(xiàn)過程
    2017-08-08
  • java 排序算法之快速排序

    java 排序算法之快速排序

    這篇文章主要介紹了java 排序算法之快速排序,文中通過圖片和代碼講解相關(guān)知識非常詳細,大家如果有需要的話可以參考一下這篇文章
    2021-09-09
  • Java算法練習題,每天進步一點點(1)

    Java算法練習題,每天進步一點點(1)

    方法下面小編就為大家?guī)硪黄狫ava算法的一道練習題(分享)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望可以幫到你
    2021-07-07
  • 使用java從亂碼文本中解析出正確的文本

    使用java從亂碼文本中解析出正確的文本

    這篇文章主要介紹了使用java從亂碼文本中解析出正確的文本的方法,需要的朋友可以參考下
    2014-04-04
  • Java實現(xiàn)多文件上傳功能

    Java實現(xiàn)多文件上傳功能

    這篇文章主要為大家詳細介紹了Java實現(xiàn)多文件上傳功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-06-06
  • java實現(xiàn)五子棋程序

    java實現(xiàn)五子棋程序

    這篇文章主要為大家詳細介紹了java實現(xiàn)五子棋程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • 使用jvisualvm配合Visual GC插件監(jiān)控Java程序詳細總結(jié)

    使用jvisualvm配合Visual GC插件監(jiān)控Java程序詳細總結(jié)

    本節(jié)將會介紹一下jvisualvm的特性及作用、各個功能是如何使用的、最后會介紹jvisualvm的插件Visual GC的安裝及使用
    2021-09-09
  • Java面試題沖刺第十四天--PRC框架

    Java面試題沖刺第十四天--PRC框架

    這篇文章主要為大家分享了最有價值的三道關(guān)于PRC框架的面試題,涵蓋內(nèi)容全面,包括數(shù)據(jù)結(jié)構(gòu)和算法相關(guān)的題目、經(jīng)典面試編程題等,感興趣的小伙伴們可以參考一下
    2021-08-08
  • Java鏈接redis_動力節(jié)點Java學院整理

    Java鏈接redis_動力節(jié)點Java學院整理

    這篇文章主要介紹了Java鏈接redis,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-08-08

最新評論