2025年3月17日,JDK 26的正式发布不仅是新特性的集合,也包含了一些容易被忽略但意义深远的改动。其中一项,就是 java.lang.Thread 类中那个声名狼藉的 stop() 方法被彻底移除了。对于一个早在JDK 1.2就被标记为废弃、背负了整整28年“历史包袱”的API,这次物理删除无疑标志着Java在追求现代化与安全性的道路上,迈出了坚定的一步。
JDK-8368226 到底做了什么?
在JDK 26的众多变更中,JDK-8368226显得不那么起眼,但其影响却是深远的。这项变更的核心内容是:Thread.stop() 方法(无参版本)被从JDK源代码、字节码及运行时中彻底移除。

根据OpenJDK官方发布的说明,这意味着:
- 编译层面:任何调用
Thread.stop() 的源代码将无法通过JDK 26的编译。
- 运行层面:已用旧版本JDK编译的字节码,在JDK 26上运行时,会抛出
NoSuchMethodError(而非之前版本可能抛出的 UnsupportedOperationException)。
换言之,这不再是一次温和的“警告”或“废弃”,而是一次彻底的“告别”。
黑历史回顾:为何28年才移除?
要理解这次移除的分量,我们需要回顾 Thread.stop() 跌宕起伏的“一生”。
| 时间节点 |
事件 |
意义 |
| 1998年 |
JDK 1.2 发布,Thread.stop() 被标记为 @Deprecated |
官方首次承认其存在安全隐患。 |
| 2004年 |
Java 官方发布《Why Are Thread.stop, Thread.suspend and Thread.resume Deprecated?》文档 |
系统性地阐述了其不安全的原因,并推荐使用协作式中断。 |
| 2022年 |
JDK 18 发布,将其标记为 @Deprecated(forRemoval = true) |
正式进入“移除倒计时”阶段。 |
| 2023年 |
JDK 20 发布,改为无条件抛出 UnsupportedOperationException |
在运行时层面将其禁用。 |
| 2025年 |
JDK 26 发布,方法被彻底移除 |
长达28年的“废弃”生涯正式终结。 |
从被标记废弃到最终删除,横跨了28年,这在编程语言史上也堪称罕见。这背后固然有Java对向后兼容近乎执拗的坚持,但也侧面说明了该API曾经被使用的广泛程度以及移除它的复杂性。
为什么 Thread.stop() 如此危险?
官方文档对其的评价一针见血:This method is inherently unsafe(这个方法本质上就是不安全的)。
核心机制与致命缺陷
当你调用 thread.stop() 时,它会强制执行两个动作:
- 即刻抛出
ThreadDeath 异常:这个异常可以在被停止线程的 run() 方法中的任何执行点抛出,包括 synchronized 块、catch 或 finally 块内部。
- 强制释放所有持有的锁:线程持有的所有监视器(Monitor)会被立即解锁。
数据损坏的典型案例
让我们通过一个简化的银行转账场景来理解其危害:
public class BankTransfer {
private double balance = 1000.0;
public synchronized void transfer(double amount) {
// 步骤1:扣减余额
this.balance -= amount; // 假设 amount=500,此时 balance = 500
// 步骤2:模拟耗时操作(如调用外部API、写入日志)
Thread.sleep(5000); // 在此期间,线程被调用了 stop()!
// 步骤3:记录交易流水(由于stop,永远执行不到了)
logTransaction(amount);
}
}
假设在 sleep 期间,另一个线程强制 stop() 了这个正在执行转账的线程,会发生什么?
- 交易未完成:步骤3的记录流水操作被跳过。
- 数据已变更:步骤1的扣款操作已经生效(
balance 变为 500)。
- 锁被释放:由于锁被强制释放,其他线程可以立即读取到处于
balance=500 但无任何交易记录的不一致状态的对象。
这就是官方文档中提到的 damaged objects(受损对象)——对象内部状态被破坏,并暴露给其他线程,最终可能导致任意、不可预测的行为,且问题在事发后极难追踪和复现。
为何无法通过捕获异常来补救?
你可能会想,在 catch (ThreadDeath e) 块中修复对象状态不就行了?理论上或许可以,但实践层面几乎不可能,原因有二:
- 异常抛出点不可控:
ThreadDeath 可能在任何一条指令后抛出,这意味着你需要对每一个同步块、每一个可能的状态变更点都设计复杂的恢复逻辑。
- 清理过程可能再次被中断:在
catch 或 finally 块中进行清理时,同样可能再次抛出 ThreadDeath,导致需要无限递归地进行清理,直到线程真正“死透”。
此外,ThreadDeath 是 Error 的子类,而非 Exception,默认情况下它不会打印堆栈跟踪,这使得因它导致的问题更加隐蔽。
Java 社区与技术演进的共识
Thread.stop() 的移除并非一时兴起,而是整个 Java 社区多年来形成的共识,也是语言自身演进的必然结果。
现代并发编程范式的推动
随着 Project Loom(虚拟线程) 的引入和结构化并发概念的普及,Java 的并发模型正朝着更安全、更可控的方向发展。Thread.stop() 这种暴力的、非协作的线程终止方式,与结构化并发所倡导的生命周期清晰、可预测、可组合的理念完全背道而驰。在现代化的多线程编程实践中,线程应是“合作者”,而非需要被“击杀”的目标。
Java 平台安全性的整体提升
这一移除与 JDK 近期的其他安全强化措施一脉相承。例如,旨在未来禁止通过深度反射修改 final 字段的 JEP 500。这些动作共同传递出一个信号:Java 正在系统性地收紧那些可能破坏对象完整性、绕过语言安全机制的“后门”操作,以提升整个平台的可信度与健壮性。对于开发者而言,深入理解这些变化,也是提升自身程序开发能力的重要一环。
如何正确停止线程?现代方案一览
既然危险的 stop() 已成历史,我们应该采用哪些安全的方式来管理线程生命周期呢?
方案一:协作式中断(Cooperative Interruption)
这是最经典和推荐的方式,利用 Thread.interrupt() 和中断状态检查。
public class SafeStop {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
// 定期检查中断状态
while (!Thread.currentThread().isInterrupted()) {
try {
doWork();
} catch (InterruptedException e) {
// 收到中断信号,执行资源清理后退出
System.out.println("收到中断信号,准备退出...");
cleanup();
break; // 或直接 return
}
}
});
worker.start();
Thread.sleep(1000);
worker.interrupt(); // 发送中断请求,而非强制停止
}
}
方案二:volatile 标志位
对于自行控制的循环任务,使用 volatile 布尔标志是更清晰的做法。
public class VolatileStop {
private volatile boolean running = true;
public void stop() {
running = false;
}
public void run() {
while (running) {
doWork();
}
cleanup(); // 循环结束后安全清理
}
}
方案三:结构化并发(JDK 21+)
对于 JDK 21 及以上版本,StructuredTaskScope 提供了更优雅的解决方案,它能自动管理子线程的生命周期。
// 使用 StructuredTaskScope 自动管理子线程生命周期
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
scope.fork(() -> {
// 子任务1
return doTask1();
});
scope.fork(() -> {
// 子任务2
return doTask2();
});
scope.join(); // 等待所有任务完成或失败
scope.throwIfFailed();
} // 作用域结束时,所有子线程会被自动、安全地停止
对现有项目的影响与迁移建议
影响范围评估
- 直接调用:源代码无法编译,必须修改。
- 反射调用:运行时抛出
NoSuchMethodError。
- 第三方依赖:某些陈旧的库或框架可能内部使用了此方法,需检查其兼容性。
迁移步骤
- 代码扫描:在项目中全局搜索
.stop() 调用。
- 逻辑替换:将
stop() 替换为基于 interrupt() 的协作式停止机制,并确保线程代码能正确响应中断。
- 充分测试:重点测试线程停止过程中的资源释放和状态一致性。
- 升级依赖:确保项目所使用的第三方库已适配 JDK 26。
临时回退方案
如果确有无法立即改造的遗留代码需要运行,可考虑:
- 暂时运行在 LTS 版本 JDK 25 上。
- 极端情况下,可使用字节码操作工具(如 ASM)重写
stop() 的调用,将其映射到自定义的安全停止逻辑。
结语:告别过去,迎接更安全的并发未来
Thread.stop() 的移除,是 Java 并发编程演进史上的一个重要注脚。它宣告了那种强制、不可控的线程管理方式正式退出历史舞台,取而代之的是协作、结构化的现代并发模型。
这对于开发者而言,短期可能意味着一些代码审计和改造的工作,但长期来看,它迫使我们在设计之初就采用更健壮、更安全的线程交互模式,从而写出更可靠的程序。正如社区里流传的那句话:“如果你觉得需要 Thread.stop(),那你真正需要的可能是一次代码重构。”
28年的漫长等待,这项“祖传”的技术债终于被偿还。这不仅仅是移除一个方法,更是 Java 平台在保持企业级稳定性的同时,勇于革新、持续向现代化和安全化迈进的有力证明。对于所有Java开发者来说,理解并跟上这样的变化,是我们持续精进、驾驭这门经典语言的必经之路。
如果你想了解更多关于 JDK 新特性、并发编程实践或是其他深度技术解析,欢迎来到 云栈社区 与更多开发者一起交流探讨。
参考资料
- OpenJDK JDK 26 Release Notes:
https://jdk.java.net/26/release-notes
- Java 官方文档《Why Are Thread.stop, Thread.suspend and Thread.resume Deprecated?》
https://stackoverflow.com/questions/8464368/how-can-i-stop-threads-created-with-an-anonymous-class
https://bugs.openjdk.org/browse/JDK-8368226