如何正确中断一个线程?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() 因破坏锁语义被废弃。写可中断代码的核心,就是在循环条件或阻塞点上留出"响应中断"的入口。