Like Share Discussion Bookmark Smile

J.J. Huang   2019-04-23   Spring Boot   瀏覽次數:

SpringBoot - 第三十五章 | @Async 實現異步調用:ThreadPoolTask​​Scheduler線程池的優雅關閉

我們在前面 SpringBoot - 第三十四章 | @Async 實現異步調用:自定義線程池 ,介紹如何使用@Async註解來實現異步調用了。但是發現了不少異步任務沒有正確處理而導致的不少問題,這邊針對ThreadPoolTask​​Scheduler線程池說明線程池的優雅關閉。

這邊繼續沿用 chapter34 的專案來做範例。

這邊也會用到Redis的服務,如果沒有Redis的服務,可以參考此篇使用Docker建立Redis服務。Docker - 第五章 | 安裝Redis

Spring整合使用Redis請參考SpringBoot - 第二十二章 | Redis的集成和使用(一)


補充說明

Spring線程池

Spring 已經實現的線程池:

線程池 說明
SimpleAsyncTaskExecutor 不是真的線程池,這個類不重用線程,每次調用都會建立一個新的線程。
SyncTaskExecutor 這個類沒有實現異步調用,只是一個同步操作。只適用於不需要多線程的地方
ConcurrentTaskExecutor Executor的適配類,不推薦使用。如果ThreadPoolTask​​Executor不滿足要求時,才用考慮使用這個類
ThreadPoolTask​​Scheduler 可以使用cron表達式
ThreadPoolTask​​Executor 最常使用,推薦。其實質是對java.util.concurrent.ThreadPoolExecutor的包裝

ThreadPoolTask​​Executor和ThreadPoolTask​​Scheduler都是ThreadPoolExecutor的包裝,區別是ThreadPoolTask​​Scheduler實現了TaskScheduler接口,僅僅多實現這個接口

ThreadPoolTaskExecutor:

ThreadPoolTaskScheduler:

spring通過接口TaskExecutor和TaskScheduler這兩個接口的方式為異步定時任務提供了一種抽象。

TaskExecutor 接口擴展自java.util.concurrent.Executor 接口。 TaskExecutor 被建立來為其他組件提供線程池調用的抽象。

TaskExecutor是spring task的第一個抽象,實際上TaskExecutor就是為區別於Executor才引入的,而引入TaskExecutor的目的就是為定時任務的執行提供線程池的支持

TaskScheduler是spring task的第二個抽象,那麼從字面的意義看,TaskScheduler就是為了提供定時任務的支持,好處是讓需要執行定時任務的代碼不需要指定特定的定時框架(比如Timer和Quartz)

問題說明

我們定義了一個線程池,然後利用@Async註解寫了3個任務,並指定了這些任務執行使用的線程池。在上文的單元測試中,我們沒有具體說說shutdown相關的問題,下面我們就來模擬一個問題現場出來。

定義線程池

修改 AsyncTask

建立 測試案例

通過for循環往上面定義的線程池中提交任務,由於是異步執行,在執行過程中,利用System.exit(0)來關閉程序,此時由於有任務在執行,就可以觀察這些異步任務的銷毀與Spring容器中其他資源的順序是否安全。

測試結果

我們可以發現ThreadPoolTaskScheduler:Shutting down後就沒有再正執行其他任務了。

1
2019-04-23 10:12:39.949  INFO 1498 --- [       Thread-2] o.s.s.c.ThreadPoolTaskScheduler          : Shutting down ExecutorService 'taskExecutor'

解決方法

設定線程池

參數 說明
setWaitForTasksToCompleteOnShutdown 設置是否等待計劃任務在關閉時完成,不中斷正在運行的任務並執行隊列中的所有任務。默認為“false”,通過中斷正在進行的任務和清除隊列立即關閉。 如果你希望以更長的關閉階段為代價完全完成任務,請將此標誌切換為“true”。請注意,在正在進行的任務完成時,Spring的容器關閉會繼續。 如果你希望此執行程序在容器的其餘部分繼續關閉之前阻止並等待任務終止 - 例如 為了保持你的任務可能需要的其他資源 - 設置“awaitTerminationSeconds”屬性而不是此屬性或除此屬性之外。
setAwaitTerminationSeconds 設置此執行程序在關閉時應阻止的最大秒數,以便在容器的其餘部分繼續關閉之前等待剩餘任務完成執行。如果你的剩餘任務可能需要訪問也由容器管理的其他資源,則此功能尤其有用。默認情況下,此執行程序將不會等待任務終止。它將立即關閉,中斷正在進行的任務並清除剩餘的任務隊列 - 或者,如果“waitForTasksToCompleteOnShutdown”標誌已設置為true,它將繼續完全執行所有正在進行的任務以及隊列中的所有剩餘任務,與其他容器關閉平行。在任何一種情況下,如果使用此屬性指定等待終止時間段,則此執行程序將等待給定時間(最大)以終止任務。根據經驗,如果同時將“waitForTasksToCompleteOnShutdown”設置為true,則在此處指定明顯更高的超時,因為隊列中的所有剩餘任務仍將執行 - 與默認關閉行為相反,它只是在等待當前正在執行對線程中斷沒有反應的任務。

測試結果

可以在控制台中看到所有的線程都正常被執行完畢了。

註:以上參考了
Spring 异步执行(@Async)、任务调度(@Schedule)
程序猿DDSpring Boot使用@Async实现异步调用:ThreadPoolTaskScheduler线程池的优雅关闭 文章。