用一个工人与老板的车间故事带你彻底搞懂线程中断的底层原理。全程干货,故事+代码+源码解析三连击,看完你就能优雅“指挥”线程下班!
🛑 一、Thread.stop():老板暴力拔电源的灾难现场
故事:想象工人(线程)正在车间组装汽车(共享资源)。他刚装好引擎(x=3),正准备装轮胎(y=4)时,老板突然冲进来拔掉电源(thread.stop())。结果:引擎装上了,轮胎没装,汽车半成品出厂!其他工人(线程)看到这辆“残废车”直接崩溃。
⚠️ stop()的致命问题(源码级解析)
-
强制释放所有锁:
stop()会立即抛出ThreadDeath异常(继承自Error),并释放该线程持有的所有锁。在源码中(Thread.stop()的本地方法stop0()),JVM 会解除线程锁定的所有监视器(monitor)。java Copy synchronized void updateCar() { x = 3; // 执行完这一步后被 stop(),锁被强制释放! y = 4; // 未执行!其他线程看到 x=3 但 y=0 的破损数据! }结果:共享对象(汽车)状态不一致,程序崩溃💥 。
-
资源泄漏:
如果工人正拿着扳手(文件句柄)或占着车位(Socket连接),突然消失会导致资源永远无法释放(扳手丢了,车位废了)。 -
废弃原因:
从 Java 1.2 开始stop()被标注@Deprecated,未来版本可能直接移除。源码中调用它会抛出UnsupportedOperationException。
✅ 结论:
stop()是线程界的“砍头术”,已彻底废弃!千万别碰!
🚦 二、interrupt():老板的礼貌下班通知
故事:老板想通知工人下班,不会直接拉闸,而是发微信:“兄弟,该走了!”(thread.interrupt())。工人看到消息,会安全收尾(关设备、还工具)再离开。
🔧 interrupt() 的底层机制(源码揭秘)
-
设置中断标志位:
interrupt()核心操作是修改线程内部的 volatile boolean interrupted标志位(源码在Thread类中) :java Copy // Thread 类源码(简化) public void interrupt() { synchronized (interruptLock) { interrupted = true; // 关键:volatile 保证可见性 } // 如果线程阻塞在 sleep/wait/join,JVM 会唤醒它并抛 InterruptedException nativeInterrupt(); } -
如何响应中断?工人自主决定
-
情况1:工人正在干活(RUNNING 状态)
工人需主动检查中断标志(isInterrupted()),就像看微信:java Copy while (!Thread.currentThread().isInterrupted()) { // 继续装轮胎... } // 发现中断标志为 true,安全收尾后退出 run()注意:不检查标志的线程会无视中断!
-
情况2:工人在睡觉(BLOCKED 状态)
如果工人正在午休(sleep(1000)),老板发消息会立刻吵醒他(抛出InterruptedException),并自动清除中断标志(设为false) :java Copy try { Thread.sleep(1000); } catch (InterruptedException e) { // 被中断唤醒!但此时中断标志已被清除(false) // 正确做法:重置中断标志,让后续代码能检测到 Thread.currentThread().interrupt(); // 重新标记“老板催我走” break; // 退出循环 }
-
🛠️ 三、正确停止线程的4种姿势(附代码)
✅ 姿势1:循环检查中断标志(通用场景)
java
Copy
public class WorkerThread extends Thread {
@Override
public void run() {
while (!isInterrupted()) { // 每次循环检查老板是否喊停
System.out.println("装轮胎中...");
}
System.out.println("收拾工具下班!");
}
}
// 老板发指令
workerThread.interrupt();
适用:线程执行可拆分任务(如循环处理数据) 。
✅ 姿势2:阻塞操作捕获 InterruptedException
java
Copy
public class SleeperThread extends Thread {
@Override
public void run() {
while (!isInterrupted()) {
try {
Thread.sleep(1000); // 可能被 interrupt() 唤醒
} catch (InterruptedException e) {
// 重置中断标志并退出
Thread.currentThread().interrupt();
}
}
}
}
关键:捕获异常后必须重置中断标志(interrupt()),否则后续逻辑无法感知中断。
✅ 姿势3:volatile 标志位 + interrupt() 双保险
针对不可中断阻塞(如某些 I/O 操作):
java
Copy
public class IoThread extends Thread {
private volatile boolean stopped = false; // 自定义标志
private Socket socket;
@Override
public void run() {
while (!stopped && !isInterrupted()) {
socket.read(); // 不可中断的 I/O
}
socket.close(); // 安全释放资源
}
// 外部停止方法
public void cancel() {
stopped = true;
interrupt(); // 双保险:尝试中断阻塞
}
}
✅ 姿势4:线程池专属停止术(Future.cancel)
用线程池时,直接操作 Future :
java
Copy
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future = executor.submit(() -> {
while (!Thread.interrupted()) { /* 任务 */ }
});
// 优雅停止
future.cancel(true); // true 表示发送中断信号
executor.shutdown();
🏭 四、线程池如何停止线程?项目组解散流程
故事:老板(主线程)要解散项目组(线程池),需走标准流程:
-
shutdown():温柔通知
java Copy executor.shutdown(); // 停止接收新任务,等现有任务完成→ 相当于通知项目组:“做完手头活就下班” 。
-
shutdownNow():紧急叫停
java Copy List<Runnable> unfinished = executor.shutdownNow();→ 相当于:“所有人立刻停下手头工作!把没做完的报表(未执行任务)交上来!”
底层:遍历工作线程,逐个调用thread.interrupt()。
💎 五、总结:停止线程的黄金法则
| 场景 | 正确做法 | 错误做法 |
|---|---|---|
| 循环任务 | while (!isInterrupted()) | stop() |
| 阻塞中(sleep/wait) | 捕获 InterruptedException + 重置标志 | 忽略异常 |
| 不可中断 I/O | volatile标志 + interrupt()双保险 | 只设标志不管阻塞 |
| 线程池任务 | future.cancel(true) | 直接操作底层 Thread |
✨ 一句话精髓:线程的停止权应交由他人(其他线程),自己只需“听话”地检测信号并安全退出。这就是 Java 中断机制设计的哲学——协作式取消(Cooperative Cancellation) 。
最后送你一句源码箴言:
🧠 “不要命令线程去死,而是请求它该死了,让它自己死得体面。” —— Java 并发设计者的潜台词
搞懂了这套机制,你写的多线程代码就能像老工匠的车间一样:高效运转,优雅收场。