Java優雅關閉執行緒池

JaxYoun發表於2024-04-28

一、背景:

執行緒池中有任務正在執行,此時需要關閉或重啟應用,池中的任務如何處理,需要考慮任務的損失、關閉速度兩個方面考慮。

推薦使用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. 任務優先:保證任務能執行完畢

1.waitForTasksToCompleteOnShutdown設為true。
2.awaitTerminationMillis超時時間設定長一點。
3.任務佇列儘量小一點(以提高關閉速度)。

  1. 速度優先:儘可能快的關閉執行緒池

1.waitForTasksToCompleteOnShutdown設為false。
2.假如單個任務比較耗時,需要將任務定義為“中斷友好”。
3.實現RunneableFuture介面的cancel方法。
4.awaitTerminationMillis設得短一點,即便任務取消失敗,也能利用超時機制關閉執行緒池。
5.任務佇列容量小一些,以減小損失。

強制殺死應用程序的情形,Spring也幫不上忙,此時要做到:

1.做好任務執行記錄,方便從斷點繼續執行。
2.儘量保證任務的冪等性,即便全部重執行,也不會引入副作用。

相關文章