如何正确中断一个线程?interrupt、isInterrupted 与 stop 的区别?

#Java#面试八股

如何正确中断一个线程?interrupt、isInterrupted 与 stop 的区别?

结论先行:Java 的中断是协作式的:interrupt() 只是给线程打一个"中断标志"(或唤醒它正在等待的阻塞),不会强制停止。线程必须自己检查 isInterrupted() 或响应 InterruptedException 来退出。stop() 已被废弃——它会直接杀死线程并释放锁,导致数据处于不一致状态。

1. 三个方法的分工

方法作用是否清除中断标志
interrupt()设置中断标志;若线程阻塞在 sleep/wait/join,唤醒并抛 InterruptedException阻塞场景会清除,运行场景不清
isInterrupted()查询某线程的中断状态(实例方法)否
Thread.interrupted()查询当前线程并清除标志(静态方法)是
stop()强制终止,释放它持有的所有锁已废弃,禁止使用
suspend()/resume()挂起/恢复已废弃,极易死锁

2. 中断的两种表现

情形 A:线程正在运行
  T.interrupt() ──▶ 中断标志 = true ──▶ 线程继续跑,直到自己检查 isInterrupted()

情形 B:线程阻塞在 sleep/wait/join
  T.interrupt() ──▶ 立即抛出 InterruptedException
                     └── 同时清除中断标志(这一点最容易踩坑)

情形 B 的"自动清除标志"是设计使然:异常本身就是通知,标志没必要保留。但它带来一个后果——如果你捕获异常后不重新设置标志,上层就再也感知不到中断了。

3. 正确的处理模板

// 模板一:循环里检查标志,用于长任务
public void run() {
    while (!Thread.currentThread().isInterrupted()) {
        doChunk();
    }
    // 退出前做清理
    cleanup();
}

// 模板二:捕获 InterruptedException 后恢复中断标志
public void waitForSignal() {
    try {
        queue.take();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();   // 必须恢复!否则中断信号丢失
        return;                               // 或者继续向上抛
    }
}

// 模板三:不可中断的操作,用"中断 + 主动关闭资源"组合
public void readLoop(Socket socket) throws IOException {
    while (!Thread.currentThread().isInterrupted()) {
        int n = socket.getInputStream().read(buf);   // 阻塞读
        if (n < 0) break;
    }
}

4. 为什么不能调 stop()

Thread.stop()
   ├── 立即抛出 ThreadDeath 错误
   ├── 强制释放该线程持有的所有 monitor 锁
   └── 临界区执行到一半 → 数据只改了一半 → 其他线程看到脏数据

stop() 的问题不是"粗暴",而是破坏了锁的原子性保证。同理,suspend() 会抱着锁睡觉,resume() 前所有需要这把锁的线程全部卡死。JDK 把它们标记为废弃,就是因为无法安全实现。

5. 常见追问

追问一:为什么捕获 InterruptedException 后要调 Thread.currentThread().interrupt()? 因为异常抛出时中断标志已被清除。如果这个方法只是中间层(比如工具类),不恢复标志,调用方就失去了"该停了"的信号,shutdownNow() 也无法真正停下任务。

追问二:中断能终止死循环吗? 不能。while(true) { compute(); } 里如果从不检查标志,interrupt() 对它毫无作用。这是"协作式"的代价:线程必须配合。

追问三:中断对 IO 阻塞有效吗? 对 InterruptibleChannel 有效(会抛 ClosedByInterruptException 并关闭通道);对普通 Socket 的 read() 无效,需要主动 close() socket 来打破阻塞。这也是为什么很多框架在 shutdown 时要遍历连接逐个关闭。

追问四:线程池的 shutdownNow() 是怎么中断的? 它遍历所有 worker 调 interrupt(),并返回队列里未执行的任务。但任务是否真的停下,仍取决于任务自身有没有响应中断。

小结

记住三句话:中断是"建议"不是"命令";InterruptedException 抛出时标志已被清除,中间层必须重新置位;stop() 因破坏锁语义被废弃。写可中断代码的核心,就是在循环条件或阻塞点上留出"响应中断"的入口。

笔记加载中…