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