2026/2/19 6:33:12
网站建设
项目流程
做炭化料的网站,做设计必知网站,秦皇岛市有几个区几个县,成都市四方建设工程监理有限公司网站关于 interrupt() 你不可忽视的细节
引言
在很多开源代码中都会看到 Thread.currrentThread().interrupt() 这串代码#xff0c;可是你真的了解它有什么用处吗#xff1f;今天我们就一起来看看这串代码到底是干嘛的。
以下这串代码是 Spring AI Alibaba 的 ModelRetryInterce…关于 interrupt() 你不可忽视的细节引言在很多开源代码中都会看到Thread.currrentThread().interrupt()这串代码可是你真的了解它有什么用处吗今天我们就一起来看看这串代码到底是干嘛的。以下这串代码是 Spring AI Alibaba 的ModelRetryIntercepter类的片段。try { log.info(Retry after {} ms, currentDelay); Thread.sleep(currentDelay); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(Retry interrupted, e); }消失的信号 – 线程的中断标记位要理解这个问题首先要明白 Java 的中断Interruption是一种协作机制而不是强制命令。它依赖于线程对象内部的一个布尔值标记位InterruptStatus。当我们调用t.interrupt()时仅仅是把这个标记位设为true。然而Java 的阻塞库函数如Thread.sleep()Object.wait()BlockingQueue.take()对中断非常敏感。它们的处理逻辑通常包含以下两个原子步骤检测到中断信号发现中断标记位为true。清除标记并抛出异常为了响应这个中断它们会先将标记位重置为 false然后抛出InterruptedException。这就是问题的核心当你捕获到InterruptedException时线程的中断标记位已经被 JVM 自动重置为false了。如果你在catch块中什么都不做Swallow the exception或者只是简单打印日志那么线程看起来就像是从未被中断过一样。结果 – “僵尸线程”让我们看一个典型的生产环境 Bug。假设你需要在一个线程中不断处理任务直到线程池关闭publicvoidrun(){// 依靠中断状态来判断是否结束while(!Thread.currentThread().isInterrupted()){try{// 模拟执行任务doWork();// 模拟阻塞Thread.sleep(1000);}catch(InterruptedExceptione){// 错误做法只是打印日志没有恢复状态System.out.println(收到停止信号但我把信号搞丢了...);}}System.out.println(线程退出);}发生了什么外部调用future.cancel(true)或executor.shutdownNow()发送中断信号。sleep()此时被唤醒它清除标志位变回false抛出异常。进入catch块打印日志。代码继续运行回到while循环头部。检查!isInterrupted()由于刚才标志位被清除了这里返回true即认为没有被中断。线程继续死循环执行无法停止。这就是所谓的僵尸线程——你以为你把它杀死了但它只是“震动”了一下抛了个异常然后像没事人一样继续跑。正确的姿势 – 恢复现场为了解决这个问题我们需要在捕获异常后手动恢复刚才丢失的中断状态。publicvoidrun(){while(!Thread.currentThread().isInterrupted()){try{doWork();Thread.sleep(1000);}catch(InterruptedExceptione){System.out.println(被中断准备退出...);// 正确做法重新设置中断状态Thread.currentThread().interrupt();// 通常配合 break 跳出循环break;}}}这行代码的作用就是告诉后续的代码包括while判断或者调用栈上层的代码“刚才发生过中断虽然异常把标志位清除了但我现在把它补回来。”那你可能有疑问为什么我不直接抛出异常你可以抛出异常但是分场景来。可以抛出时 如果你的方法签名允许抛出例如你写的是普通业务方法那优先选择抛出异常这是最正确的做法让上层决定如何处理。不能抛出时 在实现Runnable.run()接口时run方法的签名是固定的不允许抛出受检异常。你必须在内部try-catch。在这种情况下你捕获了异常就必须负责恢复状态以便调用栈上层的代码能感知到“哦原来有人想让我停止”时序问题我们通过一段代码来验证我们所说的问题体验瞬间的变化ThreadtnewThread(()-{try{System.out.println(1. 准备睡觉);Thread.sleep(5000);}catch(InterruptedExceptione){// 当我们进入这里时JVM 已经把标志位清除了System.out.println(3. 捕获异常此时标志位是: Thread.currentThread().isInterrupted());// 输出 false// 所以我们需要手动“恢复”Thread.currentThread().interrupt();System.out.println(4. 手动恢复后标志位是: Thread.currentThread().isInterrupted());// 输出 true}});t.start();Thread.sleep(100);// 确保子线程已经睡着了System.out.println(2. 主线程调用 interrupt);t.interrupt();// 这一瞬间t 的标志位其实是 true但很快就被 sleep 内部逻辑清除了假设线程 A 正在Thread.sleep(10000)Step 1:其他线程动作其他线程调用threadA.interrupt()。结果 此时线程 A 的中断标志位瞬间变为true。Step 2:线程 A (JVM 内部机制) 响应线程 A 处于sleep状态JVM 内部机制检测到了标志位变成了true。Step 3:清除状态 (关键步骤)JVM 决定唤醒线程 A。但在唤醒并抛出异常之前JVM 会做一个动作将中断标志位重置清除为false。为什么要清除就像你设定了一个闹钟闹钟响了抛出异常你醒来后第一件事是按下闹钟的停止键清除标志否则它会一直响。Step 4: 抛出异常 JVM 抛出 InterruptedException控制权进入你的catch块。结果 当代码运行到catch块里面时你如果去查isInterrupted()看到的就是false。总结永远不要生吞Swallow中断异常除非你明确知道你的线程设计就是为了忽略停止信号