如何优雅的关闭线程或线程池


如何优雅的关闭线程或线程池

在 Java 中,关闭线程或线程池需要遵循特定的规范,直接强制终止线程可能导致资源泄露或数据不一致。

以下是关闭 线程 和 线程池 的常用方法:

一、关闭单个线程的方法(不推荐直接操作,优先用线程池)

单个线程的关闭需通过 “协作式” 方式,而非强制终止(stop() 等方法已废弃,会导致线程立即终止,释放所有监视器锁,造成数据安全问题)。

1. 使用 interrupt() 配合中断标记

通过 interrupt() 方法设置线程的中断标记,线程内部通过 isInterrupted() 检测标记并主动退出。

public class InterruptThreadExample {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            // 循环检测中断标记
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    System.out.println("线程运行中...");
                    Thread.sleep(1000); // 阻塞方法会响应中断
                } catch (InterruptedException e) {
                    // 捕获中断异常后,中断标记会被清除,需手动重新设置
                    System.out.println("线程被中断,准备退出");
                    Thread.currentThread().interrupt(); // 重新设置中断标记
                }
            }
            System.out.println("线程已退出");
        });

        thread.start();
        Thread.sleep(3000); // 运行3秒后关闭线程
        thread.interrupt(); // 发送中断信号
    }
}

原理

  • interrupt() 不会直接终止线程,仅设置中断标记。
  • 线程内部需主动检测标记(isInterrupted())或响应阻塞方法(sleep()wait() 等)抛出的 InterruptedException,并在合适时机退出。

2. 使用自定义标记控制线程退出

通过自定义布尔变量作为线程运行的条件,外部修改该变量触发线程退出。

public class CustomFlagThreadExample {
    private static volatile boolean isRunning = true; //  volatile 保证可见性

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (isRunning) { // 检测自定义标记
                System.out.println("线程运行中...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println("线程已退出");
        });

        thread.start();
        Thread.sleep(3000);
        isRunning = false; // 修改标记,触发线程退出
    }
}

注意:标记需用 volatile 修饰,确保多线程间的可见性。

二、关闭线程池的方法(推荐,线程池会统一管理线程生命周期)

线程池提供了专门的方法关闭线程,确保任务有序结束并释放资源。

1. shutdown():温和关闭

  • 拒绝接收新任务,但会等待已提交的任务(包括队列中等待的任务)执行完毕后关闭线程池。
  • 调用后线程池状态变为 SHUTDOWN
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交任务...
executor.shutdown(); // 温和关闭

2. shutdownNow():强制关闭

  • 立即尝试终止所有正在执行的任务,暂停处理队列中等待的任务,并返回未执行的任务列表。
  • 通过调用线程的 interrupt() 方法中断任务,若任务不响应中断,可能无法立即终止。
  • 调用后线程池状态变为 STOP
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交任务...
List<Runnable> unexecutedTasks = executor.shutdownNow(); // 强制关闭,返回未执行的任务
System.out.println("未执行的任务数:" + unexecutedTasks.size());

3. 配合 awaitTermination() 等待关闭完成

用于等待线程池彻底关闭(所有任务执行完毕),避免主线程提前退出。

ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交任务...

executor.shutdown();
try {
    // 等待60秒,若线程池在60秒内关闭则返回true,否则返回false
    if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
        // 若超时未关闭,可尝试强制关闭
        executor.shutdownNow();
    }
} catch (InterruptedException e) {
    // 等待过程中被中断,需再次强制关闭
    executor.shutdownNow();
}

三、关键注意事项

  1. 避免使用废弃方法Thread.stop()Thread.suspend()Thread.resume() 已废弃,会导致线程状态不一致或死锁。
  2. 任务需响应中断:线程池关闭时通过 interrupt() 终止任务,若任务中包含 while(true) 等不响应中断的逻辑,可能导致线程无法关闭。
  3. 资源释放:线程退出前需手动释放持有的资源(如文件流、数据库连接等),避免泄露。
  4. 线程池关闭后不可再提交任务:调用 shutdown()shutdownNow() 后,再提交任务会抛出 RejectedExecutionException

总结

  • 单个线程:通过 interrupt() 或自定义标记实现 “协作式” 退出,禁止使用强制终止方法。
  • 线程池:优先使用 shutdown() 温和关闭,配合 awaitTermination() 确保关闭完成;必要时用 shutdownNow() 强制关闭,但需处理未执行的任务。

合理关闭线程 / 线程池是保证程序稳定性资源安全的重要环节。

JAVA-技能点
多线程