3. 线程中断机制的正确使用

线程中断机制的正确使用 学习卡片

中断机制的核心:协作式取消

Java 的线程中断不是强制杀死线程,而是一种协作式取消信号。调用 thread.interrupt() 只是把目标线程的中断标志置为 true;目标线程是否响应、何时响应,完全由它自己决定。因此“可中断的阻塞任务”必须满足两个条件:阻塞点能被中断唤醒,且唤醒后主动退出并清理资源。

下面这个任务在循环中执行可中断的阻塞操作(BlockingQueue.take()),并在 catch (InterruptedException) 中恢复中断状态、释放资源后退出:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public class InterruptibleWorker implements Runnable {

    private final BlockingQueue<String> queue = new ArrayBlockingQueue<>(8);
    private volatile boolean running = true;

    public void submit(String task) throws InterruptedException {
        queue.put(task); // 队列满时阻塞,可被中断
    }

    @Override
    public void run() {
        try {
            while (running) {
                // 阻塞点:队列空时挂起,interrupt() 会抛出 InterruptedException
                String task = queue.poll(1, TimeUnit.SECONDS);
                if (task != null) {
                    System.out.println("处理任务: " + task);
                } else {
                    System.out.println("空闲等待中...");
                }
            }
        } catch (InterruptedException e) {
            // 1. 恢复中断状态,让上层调用者也能感知
            Thread.currentThread().interrupt();
            System.out.println("收到中断信号,准备退出");
        } finally {
            // 2. 清理资源:无论正常退出还是被中断都执行
            cleanup();
        }
        System.out.println("工作线程已终止");
    }

    private void cleanup() {
        queue.clear();
        System.out.println("资源已清理,队列剩余: " + queue.size());
    }

    public void shutdown() {
        running = false;
    }
}

启动与中断的验证代码:

public class InterruptDemo {
    public static void main(String[] args) throws Exception {
        InterruptibleWorker worker = new InterruptibleWorker();
        Thread t = new Thread(worker, "worker-1");
        t.start();

        worker.submit("A");
        worker.submit("B");
        Thread.sleep(500);

        System.out.println("中断前 isInterrupted = " + t.isInterrupted());
        t.interrupt();                       // 触发中断
        System.out.println("中断后 isInterrupted = " + t.isInterrupted());

        t.join(2000);
        System.out.println("线程状态: " + t.getState());
    }
}
预期现象:线程先打印处理 A、B,随后进入空闲等待;interrupt() 后 poll 抛出 InterruptedException,进入 catch 分支打印“收到中断信号”,finally 打印“资源已清理”,最后打印“工作线程已终止”。isInterrupted() 在中断前为 false,中断后为 true。

isInterrupted() 与 interrupted() 的关键区别

两者都读取中断标志,但行为不同:

  • isInterrupted():只查询,不清除。实例方法,可反复调用,标志保持 true。
  • interrupted():查询并清除。静态方法,作用于当前线程,第一次调用返回 true,之后标志被重置为 false。

验证代码:

Thread t = new Thread(() -> {
    System.out.println("isInterrupted: " + Thread.currentThread().isInterrupted());
    System.out.println("isInterrupted: " + Thread.currentThread().isInterrupted());
    System.out.println("interrupted:   " + Thread.interrupted());
    System.out.println("interrupted:   " + Thread.interrupted());
});
t.start();
t.interrupt();
t.join();

预期输出:前两次 isInterrupted() 均为 true(标志未被清除);第一次 interrupted() 为 true 并清除标志,第二次 interrupted() 为 false。

常见错误:在 catch 块中吞掉 InterruptedException 却不恢复中断状态,会导致上层无法感知取消请求。正确做法是调用 Thread.currentThread().interrupt() 重新置位,或直接向上抛出。
中断信号传递与状态变化 主线程 main t.interrupt() t.isInterrupted() → true t.join(2000) 中断信号 工作线程 worker-1 RUNNABLE 处理任务 A、B TIMED_WAITING poll(1s) 阻塞 interrupt() 唤醒 抛出 InterruptedException 中断标志被清除 catch 块 恢复中断标志 finally 清理资源 → TERMINATED 中断标志变化 初始: false interrupt(): true 抛异常后: false 恢复后: true isInterrupted() vs interrupted() isInterrupted() 实例方法,只查询 标志保持 true interrupted() 静态方法,查询并清除 首次 true,之后 false

可复用的中断处理检查清单

检查项正确做法错误做法
阻塞点选择使用 poll(timeout)、take()、sleep() 等可中断方法使用不可中断的忙等待或 synchronized 无限等待
异常处理catch 中调用 Thread.currentThread().interrupt() 恢复标志catch 后什么都不做,吞掉中断
资源清理在 finally 中释放队列、连接、文件句柄只在正常路径清理,中断时泄漏资源
循环退出用 while(running) 配合中断标志判断忽略中断标志,继续循环
标志查询需要保留标志时用 isInterrupted()误用 interrupted() 导致标志被意外清除