Java优雅关闭线程池

一、背景:

线程池中有任务正在执行,此时需要关闭或重启应用,池中的任务如何处理,需要考虑任务的损失、关闭速度两个方面考虑。

推荐使用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.尽量保证任务的幂等性,即便全部重执行,也不会引入副作用。

posted @ 2024-04-28 18:13  JaxYoun  阅读(127)  评论(0编辑  收藏  举报