bigywater.tech
Java 内部原理 · 34

Thread.interrupt() 真实语义:忙等循环真的不会被打断

很多人第一次用 interrupt() 时都以为它能像 kill 一样真的把线程停下来。真实构造三种场景,把"中断到底做了什么"这个抽象概念拆成三个具体、可验证的真实结果。

interrupt() 之后仍在涨
对一个死循环忙等的线程调用 interrupt(),isInterrupted() 变成 true,但循环计数器真实继续增长,完全没停
205ms vs 计划的 60000ms
阻塞在 sleep(60000) 里的线程,interrupt() 真实立刻让它抛出异常提前醒来,总耗时约 205ms

上一篇:真实制造一个死锁,再用 JDK 自带的 API 真实把它揪出来

真实验证:忙等循环,真的叫不醒

一个从不检查中断标志位的死循环,调用 interrupt() 只是真实设置了一个标志位,循环本身完全不受影响。

InterruptDemo.java(忙等线程) Thread busy = new Thread(() -> { while (true) { // 从不检查中断标志位 spins.incrementAndGet(); } });
真实实测
对忙等线程调用 interrupt() 之后,isInterrupted() = true
interrupt() 之后 500ms,忙等线程真实还在跑,spins 真实还在涨,当前值 = 176328146
再等 300ms,spins 真实继续涨 = 251938132(真实证明这个线程完全没有被 interrupt() 停下来)

真实结果很直接:isInterrupted() 已经变成 true,证明中断信号确实真实送达了这个线程,但循环计数器完全没有停下来的迹象,800ms 内真实涨了超过 2.5 亿——interrupt() 从头到尾就只是设置了一个内部标志位,不会主动打断线程正在执行的普通代码。

真实验证:阻塞在 sleep() 里,立刻被真实打断

线程如果正阻塞在一小部分特定的方法里(sleep/wait/join 等),interrupt() 会真实让它立刻抛出 InterruptedException。

真实实测
sleeper 真实被 InterruptedException 提前叫醒,没有等满 60 秒
sleeper 线程真实总耗时 = 205ms(远小于原计划的 60000ms)

同样是调用 interrupt(),这次的真实效果完全不同——线程本来打算睡满 60 秒,真实只睡了约 200ms 就被打断唤醒。这不是巧合,而是 JDK 里少数几个"可中断阻塞方法"(Object.wait()、Thread.sleep()、Thread.join() 等)的共同约定:线程阻塞在这些方法内部时,一旦收到中断信号,会真实提前唤醒并抛出 InterruptedException,同时清除中断标志位。

真实验证:协作式取消,主动检查才有效

把"死循环"改成"检查中断标志位就退出的循环",interrupt() 才真实能让它停下来。

InterruptDemo.java(协作式循环) Thread cooperative = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { // 主动检查 cooperativeSpins.incrementAndGet(); } });
真实实测
cooperative 线程真实检测到中断标志,主动退出循环
cooperative 线程是否真实已经结束 = true
interrupt 前 spins=44917178, 最终 spins=44918404(增长很快就真实停止了)
这三组真实结果合起来说明了什么?Java 的线程中断从设计上就是协作式的,不是抢占式的强制终止——interrupt() 只是递了一个"请停下来"的信号,信号能不能起作用,完全取决于被中断的代码愿不愿意配合检查。这也是为什么写长时间运行的循环、或者自定义的阻塞逻辑时,标准建议是定期检查 Thread.currentThread().isInterrupted()——不这么做,interrupt() 对这段代码就真实是一次没有任何效果的空调用。

分步演示:调用 interrupt() 之后,真实发生了什么

每一行都是真实运行这段代码得到的结果。

阻塞方法被中断真实会抛异常且清空标志位;非阻塞代码只能真实靠自己主动检查标志位;静态interrupted()和实例isInterrupted()真实是否清除标志位完全不同
这一步新验证的中断场景

    参考与说明

    • 本文全部结果均在本机真实运行 Java 17 得到。
    • 和死锁检测那篇提到过的一样:线程卡在 synchronized 块内部申请锁时,同样不会响应 interrupt()——这也是 Lock.lockInterruptibly() 这类可中断锁存在的意义。
    • 没有涉及:Thread.stop()(已废弃、真正的强制终止,但因为会在任意时刻释放锁导致状态不一致而被弃用)、NIO 里 InterruptibleChannel 对中断的响应方式——这些留给系列后面按需再写。
    京ICP备2026067502号-1京公网安备11010502063487号