杰瑞科技汇

java 线程池 shutdown

Java 线程池提供了两个关闭方法:shutdown()shutdownNow()

java 线程池 shutdown-图1
(图片来源网络,侵删)
  1. shutdown()“优雅关闭”,它会停止接收新任务,但会等待正在执行的任务和队列中等待的任务执行完毕,之后,线程池才会真正关闭。
  2. shutdownNow()“立即关闭”,它会尝试停止所有正在执行的任务,并清空任务队列,返回队列中未执行的任务列表,它会尝试中断正在运行的线程(如果任务没有正确处理中断,则可能无效)。

详细讲解

shutdown() 方法

shutdown() 的执行流程如下:

  1. 停止接收新任务:调用 shutdown() 后,如果你再尝试通过 execute() 方法提交新任务,线程池会拒绝该任务,并抛出 RejectedExecutionException 异常。
  2. 继续处理已有任务:线程池会继续处理以下任务:
    • 已经开始执行的任务(isShutdown() 返回 true,但 isTerminated() 返回 false)。
    • 任务队列中等待执行的任务。
  3. 等待终止:线程池会一直等待,直到所有任务都执行完毕。
  4. 状态变更:当所有任务都执行完毕后,线程池的状态会变为 TERMINATEDisTerminated() 返回 true

shutdown() 的特点:

  • 非阻塞方法shutdown() 方法会立即返回,它不会等待所有任务完成。
  • 状态变化:调用后,线程池状态从 RUNNING 变为 SHUTDOWN

shutdownNow() 方法

shutdownNow() 的执行流程如下:

  1. 停止接收新任务:与 shutdown() 一样,它会拒绝新提交的任务。
  2. 尝试中断正在执行的任务:它会向线程池中所有正在执行任务的线程发送一个中断信号(Thread.interrupt())。注意:这取决于你的任务代码是否正确地响应了中断,如果任务代码中忽略了中断,那么任务可能会继续执行。
  3. 清空任务队列:它会从任务队列中移除并返回所有尚未开始执行的任务。
  4. 立即终止:线程池会立即尝试停止所有工作,不再等待。

shutdownNow() 的特点:

java 线程池 shutdown-图2
(图片来源网络,侵删)
  • 非阻塞方法:同样会立即返回。
  • 状态变化:调用后,线程池状态从 RUNNINGSHUTDOWN 变为 STOP
  • 返回值:返回一个 List<Runnable>,包含了队列中所有被移除的任务。

线程池状态流转图

理解线程池的状态是掌握 shutdown 的关键。ThreadPoolExecutor 使用一个 ctl 变量来控制线程池的运行状态,它是一个原子整数,高3位表示状态,其余位表示 worker 数量。

状态流转如下:

RUNNING -> SHUTDOWN -> TIDYING -> TERMINATED
   |          |
   |          |
   |<--------|
   |
   v
STOP -> TIDYING -> TERMINATED
  • RUNNING:可接收新任务,可处理队列中的任务。
  • SHUTDOWN:不可接收新任务,但可处理队列中的任务,调用 shutdown() 后进入此状态。
  • STOP:不可接收新任务,不处理队列中的任务,并尝试中断正在执行的任务,调用 shutdownNow() 后进入此状态。
  • TIDYING:当所有任务已终止,worker 数量为0时,线程池会进入此状态,此时会执行 terminated() 钩子方法。
  • TERMINATEDterminated() 方法执行完毕后,线程池最终进入此状态。

最佳实践:如何正确关闭线程池?

在实际开发中,我们通常不只用 shutdown() 或只用 shutdownNow(),而是将它们结合使用,并配合 awaitTermination() 来实现一个“优雅且超时”的关闭策略。

推荐的关闭模式:

java 线程池 shutdown-图3
(图片来源网络,侵删)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadPoolShutdownExample {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(4);
        // 模拟提交一些任务
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executor.submit(() -> {
                try {
                    // 模拟任务执行时间
                    System.out.println("Task " + taskId + " is running on " + Thread.currentThread().getName());
                    Thread.sleep(2000); // 每个任务执行2秒
                } catch (InterruptedException e) {
                    // 如果任务被中断,打印信息并退出
                    System.out.println("Task " + taskId + " was interrupted.");
                    Thread.currentThread().interrupt(); // 恢复中断状态
                }
                System.out.println("Task " + taskId + " finished.");
            });
        }
        // --- 开始关闭线程池 ---
        // 第一步:停止接收新任务
        executor.shutdown();
        // 第二步:等待任务完成,并设置超时时间
        // 这是一种更优雅的方式,它会在指定时间内等待任务完成
        try {
            // 等待60秒,直到所有任务执行完毕
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                // 如果超时后仍有任务未完成,则强制关闭
                System.err.println("线程池未在60秒内关闭,将尝试强制关闭...");
                List<Runnable> droppedTasks = executor.shutdownNow();
                System.err.println("强制关闭,还有 " + droppedTasks.size() + " 个任务未被执行。");
                // 再次等待,防止任务被卡住
                if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                    System.err.println("线程池未能完全关闭。");
                }
            }
        } catch (InterruptedException e) {
            // 如果在等待过程中,当前线程被中断,则中断等待,并尝试强制关闭线程池
            System.err.println("在等待线程池关闭时被中断,将尝试强制关闭...");
            executor.shutdownNow(); // 取消正在执行的任务
            // 重新设置中断状态,以便上层调用者感知
            Thread.currentThread().interrupt();
        }
        System.out.println("线程池已成功关闭。");
    }
}

代码解析:

  1. executor.shutdown():首先调用 shutdown(),进入“优雅关闭”模式,不再接受新任务,但会处理完队列中的任务。
  2. executor.awaitTermination(60, TimeUnit.SECONDS):这是关键一步,它会阻塞当前线程,直到线程池关闭(所有任务执行完毕)或者超时。
    • 如果在60秒内,所有任务都顺利完成了,awaitTermination 返回 true,程序继续执行。
    • 如果60秒后,仍有任务未完成,awaitTermination 返回 false
  3. 超时后的处理 (if (!executor.awaitTermination(...))):如果超时,说明有任务可能执行时间过长或被阻塞,我们调用 executor.shutdownNow() 进行“强制关闭”,尝试中断所有任务并清空队列。
  4. InterruptedException 的处理:如果在调用 awaitTermination 时,主线程被中断(另一个线程调用了 main 线程的 interrupt()),awaitTermination 会抛出 InterruptedException,我们也应该调用 shutdownNow() 来清理线程池,并且恢复当前线程的中断状态。
方法 行为 状态变化 阻塞性 返回值 适用场景
shutdown() 停止接收新任务,处理完已有任务后关闭。 RUNNING -> SHUTDOWN -> TERMINATED 非阻塞 void 希望任务能完整执行,不关心关闭耗时。
shutdownNow() 停止接收新任务,尝试中断并停止所有任务,清空队列。 RUNNING/SHUTDOWN -> STOP -> TERMINATED 非阻塞 List<Runnable> (未执行任务) 需要立即释放资源,不关心任务是否完成。
awaitTermination() 阻塞当前线程,直到线程池关闭或超时。 阻塞 boolean (是否因超时返回) 必须与 shutdown()shutdownNow() 配合使用,是实现“优雅超时关闭”的核心。

核心要点:

  • 先关后等:正确的关闭顺序是 shutdown() -> awaitTermination()
  • 超时机制awaitTermination 提供了超时控制,避免了线程池无限期等待。
  • 强制关闭:超时后,shutdownNow() 是最后的手段,用于强制释放资源。
  • 中断处理:任务代码必须正确处理中断信号,shutdownNow() 才能真正中断任务。
分享:
扫描分享到社交APP
上一篇
下一篇