91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

在Spring環境中怎么正確關閉線程池

發布時間:2023-04-04 10:58:47 來源:億速云 閱讀:126 作者:iii 欄目:開發技術

這篇文章主要介紹“在Spring環境中怎么正確關閉線程池”,在日常操作中,相信很多人在在Spring環境中怎么正確關閉線程池問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”在Spring環境中怎么正確關閉線程池”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

線程池正確關閉的姿勢

在這一節,先不討論應用中線程池該如何優雅關閉以達到優雅停機的效果,只是簡單介紹一下線程池正確關閉的姿勢

為簡化討論的復雜性,本文的線程池均是指JDK中的java.util.concurrent.ThreadPoolExecutor

正確關閉線程池的關鍵是 shutdown + awaitTermination或者 shutdownNow + awaitTermination

一種可能的使用姿勢如下:

ExecutorService executorService = Executors.newFixedThreadPool(1);
executorService.execute(() -> {
    // do task
});

// 執行shutdown,將會拒絕新任務提交到線程池;待執行的任務不會取消,正在執行的任務也不會取消,將會繼續執行直到結束
executorService.shutdown();

// 執行shutdownNow,將會拒絕新任務提交到線程池;取消待執行的任務,嘗試取消執行中的任務
// executorService.shutdownNow();

// 超時等待線程池完畢
executorService.awaitTermination(3, TimeUnit.SECONDS);

一個任務會有如下幾個狀態:

  • 未提交,此時可以將任務提交到線程池

  • 已提交未執行,此時任務已在線程池的隊列中,等待著執行

  • 執行中,此時任務正在執行

  • 執行完畢

那么,執行shutdown方法或shutdownNow方法之后,將會影響任務的狀態

shutdown

  • 拒絕新任務提交

  • 待執行的任務不會取消

  • 正在執行的任務也不會取消,將繼續執行

shutdownNow

  • 拒絕新任務提交

  • 取消待執行的任務

  • 嘗試取消執行中的任務(僅僅是做嘗試,成功與否取決于是否響應InterruptedException,以及對其做出的反應)

接下來看一下java doc對這兩個方法的描述:

shutdown: Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted. Invocation has no additional effect if already shut down.
This method does not wait for previously submitted tasks to complete execution. Use awaitTermination to do that.

shutdownNow: Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution.
This method does not wait for actively executing tasks to terminate. Use awaitTermination to do that.
There are no guarantees beyond best-effort attempts to stop processing actively executing tasks. For example, typical implementations will cancel via Thread.interrupt, so any task that fails to respond to interrupts may never terminate.

Java doc 提到,這兩個方法都不會等執任務執行完畢,如果需要等待,請使用awaitTermination。該方法帶有超時參數:如果超時后任務仍然未執行完畢,也不再等待。畢竟應用總歸要停機重啟,而不可能無限等待下去,因此超時機制是提供給用戶的最后一道底線

綜上,shutdown(Now) + awaitTermination 確實是實現線程池優雅關閉的關鍵

應用中如何正確關閉線程池

這一節內容其實才是本文要介紹的重心。上一小節內容我們知道了如何優雅關閉線程池,但那是一般意義上方法論指導,如果將線程池運用于我們的應用中,譬如Spring Boot環境中,復雜度將會變得不一樣

本一節,將會介紹線程池在Spring (Boot)環境中優雅關閉遇到的一個問題跟挑戰,以及解決方案

注:本節使用Spring Boot舉例,僅僅是因為它的應用面廣,受眾多,大家容易理解,并不代表只在該環境下才會出問題。在純Spring、甚至非Spring環境,都有可能出現問題

場景1

我們來假設一個場景,有了場景的鋪墊,對問題的理解會簡單一些

@Resource
private RedisTemplate<String, Integer> redisTemplate;

// 自定義線程池
public static ExecutorService executorService = Executors.newFixedThreadPool(1);

@GetMapping("/incr")
public void incr() {
    executorService.execute(() -> {
        // 依賴Redis進行計數
        redisTemplate.opsForValue().increment("demo", 1L);
    });
}
  • 自定義線程池,用于異步任務的執行。此處為演示方便使用Executors.newFixedThreadPool(1)生成了只有一個線程的線程池

  • 高并發請求/incr接口,每次請求該接口,都會往線程池中添加一個任務,任務異步執行的過程中依賴Redis

此時,要求停機發布新版本,按照Java System#exit 無法退出程序的問題文章,我們知道了優雅停機的一般步驟:

  • 切斷上游流量入口,確保不再有流量進入到當前節點

  • 向應用發送kill 命令,在設定的時間內待應用正常關閉,若超時后應用仍然存活,則使用kill -9命令強制關閉

  • 當JVM接收到kill命令,會喚起應用中所有的Shutdown Hooks,等待Shutdown Hooks執行完畢便可以正常關機;與此同時,應用會接著處理在途請求,以確保不會向客戶端拋出連接中斷異常,實現無感知發布

一切看起來很美好,然而&hellip;

當JVM收到kill指令后,便會喚醒所有的Shutdown Hook,而其中有一個Shutdown Hook是Spring應用在啟動之初注冊的,它的作用是對Spring管理的Bean進行回收,并銷毀IOC容器

那么問題就產生了:以我們的場景為例,線程池里的任務與Spring Shutdhwon Hook正在并發地執行著,一旦任務執行期依賴的資源先行被釋放,那任務執行時必然會報錯

在我們的場景中,就很有可能因為Redis連接被回收,從而導致redisTemplate.opsForValue().increment("demo", 1L);拋出異常,執行失敗

如圖示:

Jedis連接池先行被回收

在Spring環境中怎么正確關閉線程池

下一刻,線程池里的任務嘗試獲取Jedis連接,失敗并拋出異常

在Spring環境中怎么正確關閉線程池

在Spring環境中怎么正確關閉線程池

場景2

除了上述場景外,還有一個場景或許大家也經常會碰到:本地啟動一個定時任務,按一定頻率將數據從DB加載到Cache中

例如:

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

scheduledExecutorService.scheduleWithFixedDelay(() -> {
    // load from db and put into cache
    // ...
    
}, 100, 100, TimeUnit.MILLISECONDS);
  • 每100ms向線程池里扔一個任務

  • 任務是:從DB中取出數據,放入緩存(例如Local Cache,Redis)

在Spring Shutdown Hook執行期間,新的任務仍然會產生,又或者舊的任務未執行完畢,一旦嘗試獲取DB資源,就可能由于資源被回收而獲取失敗,拋出異常

此時的系統關閉已經不優雅&mdash;任務執行有異常,這種異常可能對業務有損,我們應盡量避免類似問題的產生,而不是抱著"算了吧,反正產生這個問題的概率很低",或者"算了吧,反正異常對我目前業務影響也不大"的態度,這是技術人的基本修養,也是對自我提高的要求&mdash;目前業務影響不大,允許不優先解決,但是期望掌握一種解決方案,將來有一天如果碰到了對業務損傷比較大的場景,可以很有底氣地說:我能行

解決方案

這個問題產生的根因,是Spring Shutdown Hook與線程池里的任務并發執行,有可能使任務依賴的資源被提前回收導致的。那么一個很直白的思路即是:在切斷流量之后,能否讓線程池先關閉,再執行Spring 的Shutdown Hook,避免依賴資源被提前回收?

順著這個思路,有三個問題需要解決:

  • 線程池如何關閉

  • 線程池如何感知Spring Shutdown Hook將要被執行

  • 如何讓線程池先于Spring Shutdown Hook關閉

對于第一個問題,本文的上一個小節線程池正確關閉的姿勢已經給出了解決方案:即shutdown(Now) + awaitTermination

對于第二個問題,Spring Shutdown Hook被觸發的時候,會主動發出一些事件,我們只要監聽這些的事件,就能夠做出相應的反應

對于第三個問題,我們只要在這些事件的監聽器中先行將線程池關閉,再讓程序走接下來的關閉流程即可

二、三涉及到Spring 的Shutdown Hook 執行過程,具體原理本篇按下不表,留待下一篇進行分析

在Spring環境中怎么正確關閉線程池

從上圖中可以看出,只要在destroyBeans之前關閉線程池即可,因此,有兩種解決方案:

  • 監聽Spring的ContextClosedEvent事件,在事件被觸發時關閉線程池

  • 實現Lifecycle接口,并在其stop方法中關閉線程池

此處以監聽ContextClosedEvent為例:

@Component
public class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> {

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
    	  // 獲取線程池
    	  // ...
    	  
    	  // 關閉線程池,并等待一段時間
        myExecutorService.shutdown();
        myExecutorService.awaitTermination(3, TimeUnit.SECONDS);
    }
}

此處大家或許能看出一些小問題:需要自行管理線程池。在Spring環境中,我們其實有更多的選擇:使用Spring提供的org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor,并將實例交給Spring管理

代碼如下:

// 將ThreadPoolTaskExecutor實例交給Spring管理
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(1);
    executor.setMaxPoolSize(1);
    
    // 告訴線程池,在銷毀之前執行shutdown方法
    executor.setWaitForTasksToCompleteOnShutdown(true);
    // shutdown\shutdownNow 之后等待3秒
    executor.setAwaitTerminationSeconds(3);
    
    return executor;
}
@Component
public class ContextClosedHandler implements ApplicationListener<ContextClosedEvent> {
    // 直接注入
    @Resource
    private ThreadPoolTaskExecutor executor;

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
    		// 關閉線程池
        executor.destroy();
    }
}

注: ThreadPoolTaskExecutor的waitForTasksToCompleteOnShutdown + awaitTerminationSeconds等于ThreadPoolExecutor的shutdown + awaitTermination,且在定義線程池時就將優雅關閉行為一同定義完畢,實現了高內聚的目的

在Spring環境中怎么正確關閉線程池

在Spring環境中怎么正確關閉線程池

在Spring中使用ThreadPoolTaskExecutor,更便捷:

  • 不用再自行管理線程池,獲取的時候也很方便,直接注入即可

  • 在需要關閉的時候,直接調用destroy方法即可實現優雅關閉

這樣,Spring就會等到線程池關閉(超時)后,才會接著往下執行Bean的銷毀、資源回收、應用上下文關閉的邏輯,確保被依賴資源不會被提前回收掉

到此,關于“在Spring環境中怎么正確關閉線程池”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

万源市| 县级市| 旬邑县| 康定县| 始兴县| 剑阁县| 娄底市| 出国| 思茅市| 兴文县| 监利县| 江西省| 灵寿县| 兰考县| 尼玛县| 乌拉特中旗| 同德县| 辽阳县| 信丰县| 石楼县| 友谊县| 永平县| 黄陵县| 淮滨县| 城口县| 富宁县| 昌都县| 汤阴县| 白朗县| 子长县| 汝南县| 安平县| 灵山县| 长葛市| 宝山区| 和田市| 玉溪市| 汝州市| 印江| 柳州市| 卓资县|