最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

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

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

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

@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還是正常運(yùn)行著的。

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

這很明顯不符常理,正常來說,System.exit這樣的指令是spring能夠感知到的,并且會(huì)執(zhí)行shutDown處理的,先來看看Spring 注冊(cè)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() {
				    //重點(diǎn)在這里獲取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新起了一個(gè)線程,加入了JVM Shutdown鉤子函數(shù)。

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

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);
 
			......
		}
	}
}	

這個(gè)時(shí)候我們猜想,是獲取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阻塞住了,一直往下追蹤,會(huì)發(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é)

整個(gè)死鎖的流程:

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

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

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

相關(guān)文章

  • 使用HttpClient調(diào)用接口的實(shí)例講解

    使用HttpClient調(diào)用接口的實(shí)例講解

    下面小編就為大家?guī)硪黄褂肏ttpClient調(diào)用接口的實(shí)例講解。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-10-10
  • Java 阻塞隊(duì)列和線程池原理分析

    Java 阻塞隊(duì)列和線程池原理分析

    這篇文章主要介紹了Java 阻塞隊(duì)列和線程池原理分析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 如何使用Java爬蟲批量爬取圖片

    如何使用Java爬蟲批量爬取圖片

    這篇文章主要介紹了如何使用Java爬蟲批量爬取圖片,對(duì)于爬蟲的入門來說,圖片相對(duì)來說是比較容易獲取的,因?yàn)榇蟛糠謭D片都不是敏感數(shù)據(jù),所以不會(huì)遇到什么反爬措施,對(duì)于入門爬蟲來說是比較合適的,需要的朋友可以參考下
    2023-04-04
  • 詳解Java中Stream流的用法和原理

    詳解Java中Stream流的用法和原理

    最近編碼的時(shí)候用到了Stream這個(gè)東西,以前也用過,但是對(duì)它沒有一個(gè)系統(tǒng)的認(rèn)知,在好奇心的驅(qū)動(dòng)下還是決定花一些時(shí)間去系統(tǒng)地學(xué)一學(xué),不了解Stream的同學(xué)可以看看本文,對(duì)大家的學(xué)習(xí)和工作有一定的幫助
    2023-10-10
  • springboot 如何通過SpringTemplateEngine渲染html

    springboot 如何通過SpringTemplateEngine渲染html

    通過Spring的Thymeleaf模板引擎可以實(shí)現(xiàn)將模板渲染為HTML字符串,而不是直接輸出到瀏覽器,這樣可以對(duì)渲染后的字符串進(jìn)行其他操作,如保存到文件或進(jìn)一步處理,感興趣的朋友跟隨小編一起看看吧
    2024-10-10
  • JAVA演示阿里云圖像識(shí)別API,印刷文字識(shí)別-營業(yè)執(zhí)照識(shí)別

    JAVA演示阿里云圖像識(shí)別API,印刷文字識(shí)別-營業(yè)執(zhí)照識(shí)別

    最近有由于工作需要,開始接觸阿里云的云市場(chǎng)的印刷文字識(shí)別API-營業(yè)執(zhí)照識(shí)別這里我加上了官網(wǎng)的申請(qǐng)說明,只要你有阿里云賬號(hào)就可以用,前500次是免費(fèi)的,API說明很簡(jiǎn)陋,只能做個(gè)簡(jiǎn)單參考
    2019-05-05
  • Spring Boot 整合 JWT的方法

    Spring Boot 整合 JWT的方法

    這篇文章主要介紹了Spring Boot 整合 JWT的方法,文中實(shí)例代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07
  • 詳解基于SpringBoot使用AOP技術(shù)實(shí)現(xiàn)操作日志管理

    詳解基于SpringBoot使用AOP技術(shù)實(shí)現(xiàn)操作日志管理

    這篇文章主要介紹了詳解基于SpringBoot使用AOP技術(shù)實(shí)現(xiàn)操作日志管理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • Java設(shè)計(jì)模式中的代理設(shè)計(jì)模式詳細(xì)解析

    Java設(shè)計(jì)模式中的代理設(shè)計(jì)模式詳細(xì)解析

    這篇文章主要介紹了Java設(shè)計(jì)模式中的代理設(shè)計(jì)模式詳細(xì)解析,代理模式,重要的在于代理二字,何為代理,我們可以聯(lián)想到生活中的例子,比如秘書、中介這類職業(yè),我們可以委托中介去幫我們完成某些事情,而我們自己只需要關(guān)注我們必須完成的事情,需要的朋友可以參考下
    2023-12-12
  • rocketmq的AclClientRPCHook權(quán)限控制使用技巧示例詳解

    rocketmq的AclClientRPCHook權(quán)限控制使用技巧示例詳解

    這篇文章主要為大家介紹了rocketmq的AclClientRPCHook使用技巧示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08

最新評(píng)論

紫金县| 郯城县| 治县。| 洱源县| 望谟县| 杨浦区| 佳木斯市| 连云港市| 从江县| 无为县| 弥勒县| 嘉定区| 和龙市| 永宁县| 明光市| 达孜县| 丽水市| 德阳市| 宣恩县| 桑植县| 黔西县| 罗源县| 铜陵市| 江达县| 吉隆县| 如东县| 庆安县| 巴楚县| 陆良县| 华宁县| 舒兰市| 花莲县| 武胜县| 康乐县| 瑞昌市| 会理县| 板桥市| 黄冈市| 克拉玛依市| 和田市| 巴彦县|