线上最常见的并发事故之一:

  • 线程池关不掉
  • 任务停不下来
  • 超时后仍在后台“偷偷跑”

根因往往不是线程池参数,而是 中断(interrupt)机制没用对


1. interrupt 的本质:协作式取消(不是强杀)

Thread.interrupt() 做的事情很克制:

  • 给目标线程设置一个“中断标志位”(interrupted flag)
  • 如果目标线程正阻塞在某些可中断方法上,会让其尽快返回/抛异常

不会

  • 直接把线程杀死
  • 直接回滚业务逻辑

工程含义:

  • 想“停得下来”,必须在任务代码里 配合检查中断并尽快退出

2. 两个 API 必须分清

  • t.isInterrupted():查询指定线程的中断标志位,不清除
  • Thread.interrupted():查询 当前线程 的中断标志位,并且 会清除

常见坑:

  • 你在循环里用 Thread.interrupted() 当判断条件,结果第一次判断就把标志清了,后面再也感知不到中断。

3. 常见阻塞点遇到 interrupt 的表现

3.1 会抛 InterruptedException(并清除标志)

  • Object.wait()
  • Thread.sleep()
  • Thread.join()
  • BlockingQueue.put/take() 等阻塞队列方法
  • ReentrantLock.lockInterruptibly()

结论:

  • 捕获 InterruptedException 后,如果你要把“中断语义”往上层传递,通常要:
    • Thread.currentThread().interrupt();(回填标志)

3.2 不抛 InterruptedException(但会让你自己处理)

  • LockSupport.park():不会抛异常,但会因为中断而返回;通常你需要 Thread.interrupted()/isInterrupted() 自己判断

3.3 可能完全不响应

  • 不可中断的阻塞(取决于具体 API)
  • 卡在 native IO/某些同步原语上

工程建议:

  • 关键任务尽量使用可中断 API 或可超时 API(tryLock(timeout)get(timeout))。

4. 优雅取消的“标准模板”

4.1 循环任务:定期检查中断

1
2
3
4
5
6
7
public void run() {
while (!Thread.currentThread().isInterrupted()) {
// 做一小段可终止工作
doUnitWork();
}
// 收尾:释放资源
}

4.2 处理中断异常:回填标志 + 退出

1
2
3
4
5
6
7
8
9
10
11
12
public void run() {
try {
while (true) {
blockingWork(); // 可能抛 InterruptedException
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// 退出循环,进行清理
} finally {
cleanup();
}
}

这是很多面试题的标准答案:

  • catch 后回填,否则上层看不到中断。

5. 在线程池里“取消”一个任务:Future.cancel(true)

Future.cancel(true) 做了两件事:

  • 把 Future 标记为取消
  • 如果任务正在运行,尝试对执行它的线程 interrupt()

注意:

  • 如果任务代码不配合(不检查中断、或卡在不可中断点),取消并不会立即生效。

工程建议:

  • 对外提供超时/取消语义的任务,内部必须按第 4 节模板写。

6. 线程状态与排障(定位“卡住的线程”)

线程常见状态:

  • RUNNABLE:可能在跑,也可能在等待 IO(native)
  • BLOCKED:在等 synchronized monitor
  • WAITING/TIMED_WAITING:在 wait/join/park/sleep

排障思路(结合线程名):

  • 大量 BLOCKED:锁竞争/锁顺序问题
  • 大量 WAITING (parking):AQS/LockSupport 阻塞,可能线程池耗尽或下游慢
  • 大量 TIMED_WAITING:sleep/backoff/超时等待

7. 总结

  • 中断不是“强杀”,而是“请求停止”。
  • 能不能停下来,取决于你的任务代码有没有按规范:
    • 可中断阻塞点
    • 轮询检查中断
    • 捕获中断异常后回填标志

下一篇等待与唤醒机制会把:wait/notifyConditionLockSupport 三套机制放在一张对比表里讲清楚,并解释“为什么会虚假唤醒/信号丢失”。


面试题 / Checklist

  • interrupt() 做了什么?为什么说它是“协作式取消”而不是强杀?
  • isInterrupted()Thread.interrupted() 的差别是什么?后者为什么危险?
  • 哪些阻塞点会抛 InterruptedException?捕获后为什么通常要回填中断标志?
  • LockSupport.park() 在中断时表现如何?为什么仍要循环检查条件?
  • Future.cancel(true) 与线程中断之间是什么关系?如何确保任务“停得下来”?