一、背景:
執行緒池中有任務正在執行,此時需要關閉或重啟應用,池中的任務如何處理,需要考慮任務的損失、關閉速度兩個方面考慮。
推薦使用Spring提供的執行緒池:ThreadPoolTaskExecutor,讓Spring幫我們管理執行緒池的生命週期,初始化、監控、擴充套件、關閉。
特別在應用關閉、重啟時能實現優雅關閉執行緒池,以使我們的損失降到最低;但前提是配置合理的初始化引數。
二、原理:
在Spring中如果一個Bean類,實現了Disposable介面,並實現了其destroy方法。當Spring在銷燬這個類的Bean時,就會執行這個destroy方法。通常destroy方法用於執行一些善後工作,比如資源回收、關閉資料庫連線、關閉執行緒池、回收檔案資源等。
Spring提供的執行緒池:ThreadPoolTaskExecutor類的父類ExecutorConfigurationSupport正好實現了DisposableBean介面:
/**
* Calls {@code shutdown} when the BeanFactory destroys
* the task executor instance.
* @see #shutdown()
*/
@Override
public void destroy() {
shutdown();
}
/**
* Perform a shutdown on the underlying ExecutorService.
* @see java.util.concurrent.ExecutorService#shutdown()
* @see java.util.concurrent.ExecutorService#shutdownNow()
*/
public void shutdown() {
if (logger.isInfoEnabled()) {
logger.info("Shutting down ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
}
if (this.executor != null) {
// 是否等待任務全部執行完畢再關閉的開關
if (this.waitForTasksToCompleteOnShutdown) {
// 關閉JDK執行緒池
this.executor.shutdown();//非阻塞
}
else {
//shutdownNow()的作用:
//1.不再接收新任務
//2.清空任務佇列,並返回等待執行的任務,並取消這些任務(要支援取消,就必須實現RunneableFuture介面的cancel方法)
//3.立即中斷正在執行的任務(能中斷就中斷,否則就讓其執行完畢)
for (Runnable remainingTask : this.executor.shutdownNow()//非阻塞) {
cancelRemainingTask(remainingTask);
}
}
awaitTerminationIfNecessary(this.executor);
}
}
/**
* Wait for the executor to terminate, according to the value of the
* {@link #setAwaitTerminationSeconds "awaitTerminationSeconds"} property.
*/
private void awaitTerminationIfNecessary(ExecutorService executor) {
if (this.awaitTerminationMillis > 0) {
try {
//使用者在定義執行緒池時,需要給定一個優雅關閉的超時時間,一定要拿捏適度,否則就會
if (!executor.awaitTermination(this.awaitTerminationMillis, TimeUnit.MILLISECONDS)) {
if (logger.isWarnEnabled()) {
logger.warn("Timed out while waiting for executor" +
(this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate");
}
}
}
catch (InterruptedException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Interrupted while waiting for executor" +
(this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate");
}
Thread.currentThread().interrupt();
}
}
}
實踐策略:
- 任務優先:保證任務能執行完畢
1.waitForTasksToCompleteOnShutdown設為true。
2.awaitTerminationMillis超時時間設定長一點。
3.任務佇列儘量小一點(以提高關閉速度)。
- 速度優先:儘可能快的關閉執行緒池
1.waitForTasksToCompleteOnShutdown設為false。
2.假如單個任務比較耗時,需要將任務定義為“中斷友好”。
3.實現RunneableFuture介面的cancel方法。
4.awaitTerminationMillis設得短一點,即便任務取消失敗,也能利用超時機制關閉執行緒池。
5.任務佇列容量小一些,以減小損失。
強制殺死應用程序的情形,Spring也幫不上忙,此時要做到:
1.做好任務執行記錄,方便從斷點繼續執行。
2.儘量保證任務的冪等性,即便全部重執行,也不會引入副作用。