找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4159

积分

0

好友

571

主题
发表于 1 小时前 | 查看: 2| 回复: 0

2025年3月17日,JDK 26的正式发布不仅是新特性的集合,也包含了一些容易被忽略但意义深远的改动。其中一项,就是 java.lang.Thread 类中那个声名狼藉的 stop() 方法被彻底移除了。对于一个早在JDK 1.2就被标记为废弃、背负了整整28年“历史包袱”的API,这次物理删除无疑标志着Java在追求现代化与安全性的道路上,迈出了坚定的一步。

JDK-8368226 到底做了什么?

在JDK 26的众多变更中,JDK-8368226显得不那么起眼,但其影响却是深远的。这项变更的核心内容是:Thread.stop() 方法(无参版本)被从JDK源代码、字节码及运行时中彻底移除。

JDK-8368226移除Thread.stop缺陷报告

根据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() 时,它会强制执行两个动作:

  1. 即刻抛出 ThreadDeath 异常:这个异常可以在被停止线程的 run() 方法中的任何执行点抛出,包括 synchronized 块、catchfinally 块内部。
  2. 强制释放所有持有的锁:线程持有的所有监视器(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) 块中修复对象状态不就行了?理论上或许可以,但实践层面几乎不可能,原因有二:

  1. 异常抛出点不可控ThreadDeath 可能在任何一条指令后抛出,这意味着你需要对每一个同步块、每一个可能的状态变更点都设计复杂的恢复逻辑。
  2. 清理过程可能再次被中断:在 catchfinally 块中进行清理时,同样可能再次抛出 ThreadDeath,导致需要无限递归地进行清理,直到线程真正“死透”。

此外,ThreadDeathError 的子类,而非 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
  • 第三方依赖:某些陈旧的库或框架可能内部使用了此方法,需检查其兼容性。

迁移步骤

  1. 代码扫描:在项目中全局搜索 .stop() 调用。
  2. 逻辑替换:将 stop() 替换为基于 interrupt() 的协作式停止机制,并确保线程代码能正确响应中断。
  3. 充分测试:重点测试线程停止过程中的资源释放和状态一致性。
  4. 升级依赖:确保项目所使用的第三方库已适配 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



上一篇:从 Linux 内核到 Git:Linus Torvalds 如何重塑软件工程与开源协作
下一篇:JavaScript前端反调试实战:5种主流方法防止代码被逆向分析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-20 11:29 , Processed in 0.483929 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表