Bootstrap

ThreadPoolTaskExecutor 优雅关闭

ThreadPoolTaskExecutor 优雅关闭

ThreadPoolTaskExecutor 是Spring封装过的线程池,使用其可以在应用下线时,实现一定程度上的优雅关闭。

继承关系

在这里插入图片描述

ThreadPoolTaskExecutor 实现了ExecutorConfigurationSupport,而后者实行了DisposableBean将会在Bean销毁的时候触发从而触发**destroy()**其中执行了 线程池的关闭方法:shutdown()

两种策略

无限制的等待任务完成

如何配置了waitForTasksToCompleteOnShutdowntrue,那么系统将会等待所有正在执行的任务完成,并且等待队列中的任务也全部执行完成(但不会接受新的任务)

ExecutorConfigurationSupport#shutdown
public void shutdown() {
    if (logger.isInfoEnabled()) {
       logger.info("Shutting down ExecutorService" + (this.beanName != null ? " '" + this.beanName + "'" : ""));
    }
    if (this.executor != null) {
       if (this.waitForTasksToCompleteOnShutdown) {
          // 发起关闭指令,停止接收新的任务
          this.executor.shutdown();
       }
       else {
          // 发出终止指令,不接收新的任务,打断正在执行的任务(不一定真的被打断),取消队列中的任务
          for (Runnable remainingTask : this.executor.awaitTerminationIfNecessary()) {
             cancelRemainingTask(remainingTask);
          }
       }
       awaitTerminationIfNecessary(this.executor);
    }
}
ThreadPoolExecutor#shutdown
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 检查是否由权限关闭线程池
        checkShutdownAccess();
        // 将线程池状态调整为关闭,关闭后将不会接收新的任务
        advanceRunState(SHUTDOWN);
        // 打断空闲的工作者,正在工作的工作者不受影响(没有产出的员工先裁掉😂)
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}
ThreadPoolExecutor#interruptIdleWorkers
private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            // 如果线程没有被打断,并且成功获取到worker的锁
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

我们知道线程池中的任务实际由worker来完成,worker在开始执行任务时会调用lock()上锁,所以如何此时任务没有完成,这里调用tryLock()将不会获取到锁,所以这里实际上相当于并没有打断任务

有限制的等待一段时间

ExecutorService#shutdownNow
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // 将线程池状态修改为终止状态
        advanceRunState(STOP);
        // 打断所有的工作者(要跑路了,全部干掉😒)
        interruptWorkers();
        // 取消队列中的任务
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}
ExecutorService#interruptWorkers
private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 全部worker都打断
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}
ExecutorConfigurationSupport#awaitTerminationIfNecessary
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();
       }
    }
}

等待awaitTerminationMillis,这个等待的时间需要主动配置,否则不会做任何等待

ps:打算线程即调用线程的interupt()方法并不代表一定会影响任务的执行,如果线程的状态为running,实际只会设置一个标记不会真正的中断线程,就算线程 为wait状态会如果你的程序中没有对InterruptedException异常做捕获,那么实际上正在运行的任务仍然可以继续运行,所以这里的等待还是有一定的意义

实验一下

无限制的等待实验
# 以非阻塞的方式模拟延迟   
public static void workTime(long ms) {
   	final long l = System.currentTimeMillis();
    while (System.currentTimeMillis() <= l + ms) {     
    }
}
    

public static void main(String[] args) throws InterruptedException {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(1);
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.initialize();
    executor.execute(()->{
        try {
            MyUtils.workTime(3000);
            log.info("任务1执行完成");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    });
    executor.execute(()->{
        try {
            MyUtils.workTime(3000);
            log.info("任务2执行完成");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    });


    executor.shutdown();

    Thread.sleep(100000);
}

这里只设置了一个核心线程,设置了等待所有任务完成再关闭,每个任务执行需要3s,任务1提交后马上执行,任务2方法等待队列,而后马上关闭线程池模拟应用下线场景.

结果1
00:38:55.493 [main] INFO org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor - Initializing ExecutorService
00:38:55.513 [main] INFO org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor - Shutting down ExecutorService
00:38:58.514 [ThreadPoolTaskExecutor-1] INFO com.duiba.HkTest - 任务1执行完成
00:39:01.515 [ThreadPoolTaskExecutor-1] INFO com.duiba.HkTest - 任务2执行完成

结果如上,调用shutdown()后,两个任务都依赖正常完成

有限制的等待实验
// 调用线程的sleep,线程变成wait状态
public static void sleep(long ms) throws InterruptedException {
   Thread.sleep(ms);
}

public static void main(String[] args) throws InterruptedException {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(3);
    executor.setWaitForTasksToCompleteOnShutdown(false);
    executor.setAwaitTerminationSeconds(4);
    executor.initialize();
    executor.execute(()->{
        try {
            MyUtils.sleep(3000);
            log.info("任务1执行完成");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    });
    executor.execute(()->{
        try {
            MyUtils.sleep(3000);
            log.info("任务2执行完成");
        } catch (Exception e) {
            log.info("任务2:我什么都管就要完成任务");
        }
    });

    executor.execute(()->{
        try {
            MyUtils.workTime(3000);
            log.info("任务3执行完成");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    });



    executor.shutdown();

    Thread.sleep(100000);
}
结果2
00:59:28.879 [main] INFO org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor - Initializing ExecutorService
00:59:28.899 [main] INFO org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor - Shutting down ExecutorService
00:59:28.899 [ThreadPoolTaskExecutor-2] INFO com.duiba.HkTest - 任务2:我什么都管就要完成任务
Exception in thread "ThreadPoolTaskExecutor-1" java.lang.RuntimeException: java.lang.InterruptedException: sleep interrupted
	at com.duiba.HkTest.lambda$main$0(HkTest.java:38)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.duiba.utils.MyUtils.sleep(MyUtils.java:11)
	at com.duiba.HkTest.lambda$main$0(HkTest.java:35)
	... 3 more
00:59:31.900 [ThreadPoolTaskExecutor-3] INFO com.duiba.HkTest - 任务3执行完成

结果如上,如果线程处于非阻塞状态,或者压根处理中断异常,即使打算也没有任务作用,任务依旧可以正常完成(在jvm关闭之前)

;