tomcat 正常关闭_Tomcat的带有守护程序和关闭钩子的正常关闭
tomcat 正常關(guān)閉
我的最后兩個(gè)博客討論了長時(shí)間輪詢和Spring的DeferredResult技術(shù),并且為了展示這些概念,我將我的Producer Consumer項(xiàng)目中的代碼添加到了Web應(yīng)用程序中。 盡管該代碼演示了博客所提出的觀點(diǎn),但其邏輯上確實(shí)包含大量漏洞。 除了在實(shí)際的應(yīng)用程序中不會(huì)使用簡單的LinkedBlockingQueue而是會(huì)選擇JMS或其他一些具有工業(yè)實(shí)力的消息傳遞服務(wù)這一事實(shí),以及只有一個(gè)用戶可以掌握匹配更新的事實(shí)之外,還有一個(gè)問題生成行為不佳的線程,這些線程在JVM終止時(shí)不會(huì)關(guān)閉。
您可能想知道為什么這應(yīng)該是一個(gè)問題……對(duì)您來說,作為開發(fā)人員,這根本不是問題,這只是一點(diǎn)點(diǎn)草率的編程,但是對(duì)于您的其中一個(gè)操作人員而言,它可能會(huì)使生活變得不必要地困難。 這樣做的原因是,如果您有太多行為異常的線程,那么鍵入Tomcat的shutdown.sh命令將幾乎沒有效果,并且您必須通過鍵入以下命令來嚴(yán)重殺死Web服務(wù)器:
ps -ef | grep java得到pid然后
kill -9 <<pid>>…并且當(dāng)您有一個(gè)Tomcat Web服務(wù)器字段來重新啟動(dòng)所有這些額外的問題時(shí),這將變得非常痛苦。 當(dāng)您鍵入shutdown.sh您希望Tomcat停止。
在我的前兩篇博客中,我創(chuàng)建的行為不良的線程具有以下run()方法,其中第一個(gè)方法run()如下所示)的行為確實(shí)不良:
@Override public void run() { while (true) { try { DeferredResult<Message> result = resultQueue.take(); Message message = queue.take(); result.setResult(message); } catch (InterruptedException e) { throw new UpdateException("Cannot get latest update. " + e.getMessage(), e); } } }在這段代碼中,我使用了一個(gè)無限的while(true) ,這意味著線程將一直運(yùn)行并且永不終止。
@Override public void run() { sleep(5); // Sleep to allow the reset of the app to load logger.info("The match has now started..."); long now = System.currentTimeMillis(); List<Message> matchUpdates = match.getUpdates(); for (Message message : matchUpdates) { delayUntilNextUpdate(now, message.getTime()); logger.info("Add message to queue: {}", message.getMessageText()); queue.add(message); } start = true; // Game over, can restart logger.warn("GAME OVER"); }上面的第二個(gè)示例也表現(xiàn)不佳。 它將繼續(xù)從MatchUpdates列表中獲取消息,并在適當(dāng)?shù)臅r(shí)候?qū)⑵涮砑拥较㈥?duì)列中。 它們唯一的好處是,它們可能會(huì)拋出InterruptedException ,如果處理不當(dāng),將導(dǎo)致線程終止。 但是,這不能保證。
確實(shí),有一個(gè)快速修復(fù)程序……您要做的就是確保您創(chuàng)建的任何線程都是守護(hù)程序線程。 守護(hù)程序線程的定義是一個(gè)線程,它不會(huì)阻止JVM在程序完成時(shí)退出,但該線程仍在運(yùn)行。 守護(hù)程序線程的通常示例是JVM的垃圾回收線程。 要將線程轉(zhuǎn)換為守護(hù)程序線程,只需調(diào)用:
thread.setDaemon(true);…然后當(dāng)您鍵入shutdown.sh , WHAM ,所有線程將消失。 但是,這有一個(gè)問題。 如果您的守護(hù)程序線程中的一個(gè)正在做重要的事情并將其砍掉,這會(huì)丟失一些非常重要的數(shù)據(jù)怎么辦?
您需要做的是確保所有線程正常關(guān)閉,以完成當(dāng)前可能正在執(zhí)行的所有工作。 本博客的其余部分演示了針對(duì)這些錯(cuò)誤線程的修復(fù)程序,通過使用ShutdownHook優(yōu)雅地協(xié)調(diào)了它們的ShutdownHook 。 根據(jù)文檔 ,“ shutdown hook”只是一個(gè)初始化但未啟動(dòng)的線程。 當(dāng)虛擬機(jī)開始其關(guān)閉序列時(shí),它將以某種未指定的順序啟動(dòng)所有已注冊(cè)的關(guān)閉掛鉤,并使其同時(shí)運(yùn)行。” 因此,在閱讀了最后一句話之后,您可能已經(jīng)猜到您需要做的是創(chuàng)建一個(gè)線程,該線程負(fù)責(zé)關(guān)閉所有其他線程,并作為關(guān)閉鉤子傳遞給JVM。 所有這些都可以在幾個(gè)小類中通用,并且可以通過對(duì)現(xiàn)有線程run()方法執(zhí)行一些棘手的操作來實(shí)現(xiàn)。
要?jiǎng)?chuàng)建的兩個(gè)類是ShutdownService和Hook 。 我將首先演示的Hook類用于將ShutdownService鏈接到您的線程。 Hook的代碼如下:
public class Hook { private static final Logger logger = LoggerFactory.getLogger(Hook.class); private boolean keepRunning = true; private final Thread thread; Hook(Thread thread) { this.thread = thread; } /** * @return True if the daemon thread is to keep running */ public boolean keepRunning() { return keepRunning; } /** * Tell the client daemon thread to shutdown and wait for it to close gracefully. */ public void shutdown() { keepRunning = false; thread.interrupt(); try { thread.join(); } catch (InterruptedException e) { logger.error("Error shutting down thread with hook", e); } } }Hook包含兩個(gè)實(shí)例變量: keepRunning和thread 。 thread是對(duì)該線程的引用,該Hook該實(shí)例負(fù)責(zé)關(guān)閉,而keepRunning告訴該線程…繼續(xù)運(yùn)行。
Hook有兩個(gè)公共方法: keepRunning()和shutdown() 。 線程調(diào)用keepRunning()以確定是否應(yīng)該繼續(xù)運(yùn)行,而ShutdownService的shutdown鉤子線程調(diào)用shutdown()來使線程關(guān)閉。 這是兩種方法中最有趣的。 首先,它將keepRunning變量設(shè)置為false。 然后,它調(diào)用thread.interrupt()來中斷線程,迫使其引發(fā)InterruptedException 。 最后,它調(diào)用thread.join()并等待thread實(shí)例關(guān)閉。
請(qǐng)注意,此技術(shù)依賴于您所有線程的協(xié)作。 如果混合中有一個(gè)行為不佳的線程,那么整個(gè)事情可能會(huì)死機(jī)。 要解決此問題,請(qǐng)向thread.join(…)添加超時(shí)。
@Service public class ShutdownService { private static final Logger logger = LoggerFactory.getLogger(ShutdownService.class); private final List<Hook> hooks; public ShutdownService() { logger.debug("Creating shutdown service"); hooks = new ArrayList<Hook>(); createShutdownHook(); } /** * Protected for testing */ @VisibleForTesting protected void createShutdownHook() { ShutdownDaemonHook shutdownHook = new ShutdownDaemonHook(); Runtime.getRuntime().addShutdownHook(shutdownHook); } protected class ShutdownDaemonHook extends Thread { /** * Loop and shutdown all the daemon threads using the hooks * * @see java.lang.Thread#run() */ @Override public void run() { logger.info("Running shutdown sync"); for (Hook hook : hooks) { hook.shutdown(); } } } /** * Create a new instance of the hook class */ public Hook createHook(Thread thread) { thread.setDaemon(true); Hook retVal = new Hook(thread); hooks.add(retVal); return retVal; } @VisibleForTesting List<Hook> getHooks() { return hooks; } }ShutdownService是一個(gè)Spring服務(wù),其中包含一個(gè)Hook類的列表,并因此通過推斷線程負(fù)責(zé)關(guān)閉。 它還包含一個(gè)內(nèi)部類ShutdownDaemonHook ,該類擴(kuò)展了Thread 。 的一個(gè)實(shí)例ShutdownDaemonHook的施工過程中創(chuàng)建ShutdownService ,然后通過調(diào)用傳遞給JVM作為關(guān)閉掛鉤
Runtime.getRuntime().addShutdownHook(shutdownHook);ShutdownService具有一個(gè)公共方法: createHook() 。 該類要做的第一件事是確保傳遞給它的任何線程都轉(zhuǎn)換為守護(hù)程序線程。 然后,它創(chuàng)建一個(gè)新的Hook實(shí)例,傳入線程作為參數(shù),最后將結(jié)果存儲(chǔ)在列表中并將其返回給調(diào)用者。
現(xiàn)在剩下要做的唯一一件事就是將ShutdownService集成到DeferredResultService和MatchReporter ,這兩個(gè)類包含行為不良的線程。
@Service("DeferredService") public class DeferredResultService implements Runnable { private static final Logger logger = LoggerFactory.getLogger(DeferredResultService.class); private final BlockingQueue<DeferredResult<Message>> resultQueue = new LinkedBlockingQueue<>(); private Thread thread; private volatile boolean start = true; @Autowired private ShutdownService shutdownService; private Hook hook; @Autowired @Qualifier("theQueue") private LinkedBlockingQueue<Message> queue; @Autowired @Qualifier("BillSkyes") private MatchReporter matchReporter; public void subscribe() { logger.info("Starting server"); matchReporter.start(); startThread(); } private void startThread() { if (start) { synchronized (this) { if (start) { start = false; thread = new Thread(this, "Studio Teletype"); hook = shutdownService.createHook(thread); thread.start(); } } } } @Override public void run() { logger.info("DeferredResultService - Thread running"); while (hook.keepRunning()) { try { DeferredResult<Message> result = resultQueue.take(); Message message = queue.take(); result.setResult(message); } catch (InterruptedException e) { System.out.println("Interrupted when waiting for latest update. " + e.getMessage()); } } System.out.println("DeferredResultService - Thread ending"); } public void getUpdate(DeferredResult<Message> result) { resultQueue.add(result); } }此類的第一個(gè)更改是在Shutdown服務(wù)實(shí)例中自動(dòng)連線。 接下來要做的是在創(chuàng)建線程之后但在thread.start()之前,使用ShutdownService創(chuàng)建Hook的實(shí)例:
thread = new Thread(this, "Studio Teletype"); hook = shutdownService.createHook(thread); thread.start();最后的更改是將while(true)替換為:
while (hook.keepRunning()) {…告訴線程何時(shí)退出while循環(huán)并關(guān)閉。
您可能還注意到上面的代碼中拋出了一些System.out.println()調(diào)用。 這是有原因的,這是因?yàn)閳?zhí)行關(guān)閉鉤子線程的順序不確定。 請(qǐng)記住,您的類不僅試圖優(yōu)雅地關(guān)閉,而且其他子系統(tǒng)也試圖關(guān)閉。 這意味著我的原始代碼logger.info(…)失敗, logger.info(…)以下異常:
Exception in thread "Studio Teletype" java.lang.NoClassDefFoundError: org/apache/log4j/spi/ThrowableInformationat org.apache.log4j.spi.LoggingEvent.(LoggingEvent.java:159)at org.apache.log4j.Category.forcedLog(Category.java:391)at org.apache.log4j.Category.log(Category.java:856)at org.slf4j.impl.Log4jLoggerAdapter.info(Log4jLoggerAdapter.java:382)at com.captaindebug.longpoll.service.DeferredResultService.run(DeferredResultService.java:75)at java.lang.Thread.run(Thread.java:722) Caused by: java.lang.ClassNotFoundException: org.apache.log4j.spi.ThrowableInformationat org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1714)at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1559)... 6 more這是因?yàn)楫?dāng)我嘗試調(diào)用記錄器時(shí),它已經(jīng)被卸載。 同樣,如文檔所述:“ Shutdown hooks在虛擬機(jī)生命周期的微妙時(shí)間運(yùn)行,因此應(yīng)進(jìn)行防御性編碼。 尤其應(yīng)將它們編寫為線程安全的,并盡可能避免死鎖。 它們也不應(yīng)盲目地依賴可能已經(jīng)注冊(cè)了自己的關(guān)閉鉤子的服務(wù),因此可能自己處于關(guān)閉過程中。 嘗試使用其他基于線程的服務(wù),例如AWT事件調(diào)度線程,可能會(huì)導(dǎo)致死鎖。”
MatchReport類具有一些非常相似的修改。 主要區(qū)別在于hook.keepRunning()代碼位于run()方法的for循環(huán)內(nèi)。
public class MatchReporter implements Runnable { private static final Logger logger = LoggerFactory.getLogger(MatchReporter.class); private final Match match; private final Queue<Message> queue; private volatile boolean start = true; @Autowired private ShutdownService shutdownService; private Hook hook; public MatchReporter(Match theBigMatch, Queue<Message> queue) { this.match = theBigMatch; this.queue = queue; } /** * Called by Spring after loading the context. Will "kick off" the match... */ public void start() { if (start) { synchronized (this) { if (start) { start = false; logger.info("Starting the Match Reporter..."); String name = match.getName(); Thread thread = new Thread(this, name); hook = shutdownService.createHook(thread); thread.start(); } } } else { logger.warn("Game already in progress"); } } /** * The main run loop */ @Override public void run() { sleep(5); // Sleep to allow the reset of the app to load logger.info("The match has now started..."); long now = System.currentTimeMillis(); List<Message> matchUpdates = match.getUpdates(); for (Message message : matchUpdates) { delayUntilNextUpdate(now, message.getTime()); if (!hook.keepRunning()) { break; } logger.info("Add message to queue: {}", message.getMessageText()); queue.add(message); } start = true; // Game over, can restart logger.warn("GAME OVER"); } private void sleep(int deplay) { try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { logger.info("Sleep interrupted..."); } } private void delayUntilNextUpdate(long now, long messageTime) { while (System.currentTimeMillis() < now + messageTime) { try { Thread.sleep(100); } catch (InterruptedException e) { logger.info("MatchReporter Thread interrupted..."); } } } }此代碼的最終測試是在匹配更新序列中途發(fā)出Tomcat shutdown.sh命令。 在JVM終止時(shí),它將從ShutdownDaemonHook類調(diào)用shutdown鉤子。 當(dāng)此類的run()方法執(zhí)行時(shí),它將在整個(gè)Hook實(shí)例列表中循環(huán),告訴它們關(guān)閉各自的線程。 如果在服務(wù)器日志文件的末尾添加tail -f (在我的案例中為catalina.out,但Tomcat可能配置為與我不同),則將看到條目痕跡,使服務(wù)器正常關(guān)閉。
該博客隨附的代碼可在Github上找到: https : //github.com/roghughe/captaindebug/tree/master/long-poll 。
參考:來自Captain Debug's Blog博客的Tomcat的通過守護(hù)程序和Shutdown Hooks進(jìn)行的Graceful Shutdown和我們的JCG合作伙伴 Roger Hughes。翻譯自: https://www.javacodegeeks.com/2013/10/tomcats-graceful-shutdown-with-daemons-and-shutdown-hooks.html
tomcat 正常關(guān)閉
總結(jié)
以上是生活随笔為你收集整理的tomcat 正常关闭_Tomcat的带有守护程序和关闭钩子的正常关闭的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 临洮怎么读 临洮读音简介
- 下一篇: Java:使用SingletonStre