文章目录

📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
📙 作者: 编程技术圈(哇哥面试陪跑)
👉 欢迎关注、分享、评论
✔️ 持续分享更多干货内容
🌐🌏🌎➕tcmeta, 欢迎沟通交流
📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
零、引入
王二的额角沁出一层汗,手指抖着点向屏幕 —— 他写的文件下载线程占着 IO 资源不肯停,调用 Thread.interrupt () 想 “一刀砍死” 它,结果线程没断,反而抛出 InterruptedException,下载的文件成了残缺的碎片。
“这 interrupt 到底是个什么妖物!说好的中断线程,结果把我代码搅得稀烂!” 王二的怒吼震得桌上的搪瓷缸嗡嗡响。
隔壁的哇哥正用旧报纸包炒花生,闻言慢悠悠转过头,花生壳的碎屑落在桌上:“你把 interrupt 当铡刀,可它不过是递了张‘停工通知’—— 工人(线程)接不接,停不停,全看它自己;你硬逼它停,自然要闹脾气。”

点赞 + 关注,跟着哇哥把 Thread.interrupt () 的底层扒得底朝天,3 层逻辑戳破 “中断 = 杀死” 的谎言,下次再用这方法,保准让线程服服帖帖。
一、王二的 “铡刀代码”:把 interrupt 用成 “夺命符”
王二写的文件下载线程,循环读取网络流,他觉得线程 “不听话”,就直接调用 interrupt (),结果全乱了套。代码如下,透着股 “一刀切” 的蛮横:
package cn.tcmeta.interrupt;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.TimeUnit;
/**
* @author: laoren
* @description: 王二的坑:把interrupt当“杀死线程”的铡刀
* @version: 1.0.0
*/
public class InterruptMisuseSample {
// 下载线程:从网络读取文件
private static class DownloadThread extends Thread {
private final String url;
private ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
public DownloadThread(String url) {
super("Download-Thread");
this.url = url;
}
@Override
public void run() {
try {
URLConnection conn = new URL(url).openConnection();
try (InputStream in = conn.getInputStream()) {
byte[] buffer = new byte[1024];
int len;
// 循环读取数据(王二想让这里立刻停止)
while ((len = in.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
// 模拟下载耗时
TimeUnit.MILLISECONDS.sleep(10);
}
System.out.println("下载完成:文件大小=" + outputStream.size() + "字节");
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
// 王二以为捕获异常就完事,结果文件没写完
System.out.println("线程被中断,下载失败!");
e.printStackTrace();
}
}
// 获取下载的字节数据
public byte[] getContent() {
return outputStream.toByteArray();
}
}
static void main() throws InterruptedException {
// 下载一个大文件(示例用百度首页,实际可换大文件地址)
DownloadThread downloadThread = new DownloadThread("https://www.baidu.com");
downloadThread.start();
// 下载200ms后,王二觉得太慢,强行中断
TimeUnit.MILLISECONDS.sleep(200);
System.out.println("主线程:强制中断下载线程!");
downloadThread.interrupt();
// 等待线程结束,查看文件大小
downloadThread.join();
System.out.println("最终文件大小:" + downloadThread.getContent().length + "字节");
}
}
运行结果:线程没 “死”,文件残缺

王二瘫在椅子上,百思不解:“interrupt 不是中断线程吗?怎么只打断了 sleep,读取数据的循环还跑了半截?”
哇哥把包好的花生往桌上一放,纸包发出窸窣的响:“你这是犯了新手最常犯的错 —— 把 interrupt 当成‘杀死线程’的命令,可它的本质是‘设置中断标记’+‘唤醒阻塞线程’。线程在运行中时,interrupt 只给它贴个‘要中断’的标签;只有线程在 sleep、wait、join 这些阻塞状态时,才会抛异常 —— 就像你给工人递停工通知,他正干活时,顶多瞥一眼通知,继续干;只有他在歇着(阻塞)时,才会被通知吵醒,闹着要停工。”
二、用 “工厂停工” 讲透 interrupt:是 “通知” 不是 “命令”

哇哥拽过王二桌上的草稿纸,画了个歪歪扭扭的工厂车间,一个工人在搬货,墙上贴个 “停工通知” 的标签 —— 这是他的拿手好戏,再深的底层逻辑,到他手里都能变成车间里的家常事。
“线程就像工厂里的工人,CPU 是工位,interrupt 是车间主任递的‘停工通知’,” 哇哥指着草稿纸,“核心就三点:
- 贴标签:调用 interrupt (),先给工人(线程)贴个‘待停工’的标签(中断标记置为 true);
- 唤阻塞:如果工人正歇着(线程在 sleep/wait/join 等阻塞状态),就把他叫醒,还甩给他一张‘被打断’的单子(抛 InterruptedException),同时把标签撕了(中断标记置为 false);
- 不强制:工人接不接通知,停不停工,全看他自己 —— 要是他埋头干活不看标签,就算贴满通知,照样接着干。”
他用铅笔圈出 “中断标记” 四个字:“这是 interrupt 的核心 ——Thread 类里有个 volatile 的 interruptedStatus 字段(JVM 层面的标记),interrupt () 的底层就是操作这个标记,再处理阻塞状态。”
// interrupt status (read/written by VM)
volatile boolean interrupted;

三、底层逻辑三层拆解(王二记在烟盒内侧)
王二掏出皱巴巴的烟盒,用铅笔歪歪扭扭地记,字里行间都是底层真相:

❓ 第一层:中断标记的存储(Java 层面)
Thread 类里有个private volatile boolean interrupted;(JDK 源码里实际是 JVM 维护的 interruptedStatus 字段),这是个 volatile 变量,保证多线程可见性 —— 调用 interrupt (),本质是把这个标记置为 true;调用 interrupted () 会清除标记,isInterrupted () 只读取不清除。

public static boolean interrupted() {
return currentThread().getAndClearInterrupt();
}

public boolean isInterrupted() {
return interrupted; // 只读取
}
➡️ 第二层:interrupt () 的核心行为(JVM 层面)
JDK 源码里,Thread.interrupt () 的核心逻辑如下(简化版):
public void interrupt() {
// Setting the interrupt status must be done before reading nioBlocker.
interrupted = true;
interrupt0(); // inform VM of interrupt
// thread may be blocked in an I/O operation
if (this != Thread.currentThread()) {
Interruptible blocker;
synchronized (interruptLock) {
blocker = nioBlocker;
if (blocker != null) {
blocker.interrupt(this);
}
}
if (blocker != null) {
blocker.postInterrupt();
}
}
}
JVM 执行 interrupt0 () 时,会做两件事:
- 给线程设置中断标记;
- 如果线程处于阻塞状态(比如调用了 park ()),就唤醒线程。
👉 第三层:阻塞线程的中断处理(OS 层面)
当线程在 sleep/wait/join 时,会陷入操作系统的 “阻塞态”,JVM 调用 OS 的中断机制(比如 Linux 的 pthread_kill)唤醒线程,同时清除中断标记,抛出 InterruptedException—— 这是 OS 和 JVM 协同的结果,目的是让阻塞线程能响应中断。
四、代码示例:把 interrupt 用对,让线程 “优雅停工”
哇哥拿过王二的鼠标,说:“想让线程听话,得两步走:一是调用 interrupt () 贴标签,二是让线程自己检查标签,优雅停工。这三个示例,把 interrupt 的用法扒得明明白白。”
✅ 示例 1:正确终止运行中的线程(检查中断标记)
package cn.tcmeta.interrupt;
import java.util.concurrent.TimeUnit;
/**
* @author: laoren
* @description: 正确用法:线程主动检查中断标记,优雅终止
* @version: 1.0.0
*/
public class InterruptCorrectSample {
// 任务线程:循环处理数据,主动检查中断标记
private static class TaskThread extends Thread {
public TaskThread() {
super("Task-Thread");
}
@Override
public void run() {
long count = 0;
while (!Thread.currentThread().isInterrupted()) {
// 模拟处理数据
count++;
if (count % 10000000 == 0) {
System.out.println(this.getName() + ": 已处理" + count / 10000 + "万次");
}
// 每处理一定次数后才休眠,避免过早被中断
if (count % 1000000 == 0) {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println(this.getName() + ": 阻塞时被中断,准备终止");
// 恢复中断状态并在退出前完成最后一次计数
Thread.currentThread().interrupt();
count++; // 补偿中断时的处理
break;
}
}
}
System.out.println(this.getName() + ": 优雅终止,最终处理了" + count / 10000 + "万次");
}
}
static void main() throws InterruptedException {
TaskThread taskThread = new TaskThread();
taskThread.start();
// 运行3秒后,发送中断通知
TimeUnit.SECONDS.sleep(3);
System.out.println("主线程: 发送中断通知");
taskThread.interrupt();
// 等待线程终止
taskThread.join();
System.out.println("主线程: 任务线程已终止");
}
}

哇哥解释:“关键是两点:一是循环条件检查 isInterrupted (),二是捕获 InterruptedException 后重置标记 —— 因为异常会清除标记,不重置的话,循环会继续跑。就像工人被叫醒后,把通知撕了,你得重新贴一张,他才知道要停工。”
💯 示例 2:interrupted () 和 isInterrupted () 的区别(面试必考)
王二总把这两个方法弄混,哇哥干脆写了段对比代码,让他一眼看明白:
package cn.tcmeta.interrupt;
import java.util.concurrent.TimeUnit;
/**
* @author: laoren
* @description: 对比:interrupted() vs isInterrupted()
* @version: 1.0.0
*/
public class InterruptMethodCompare {
static void main() {
Thread thread = new Thread(() -> {
// 循环检查标记
while (!Thread.interrupted()) { // 注意:用的是Thread.interrupted()
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
// 异常会清除标记,interrupted()会返回false,循环继续
System.out.println("线程被中断,interrupted()=" + Thread.interrupted());
}
}
System.out.println("线程终止");
}, "Test-Thread");
thread.start();
try {
TimeUnit.SECONDS.sleep(1);
thread.interrupt(); // 发送中断通知
TimeUnit.SECONDS.sleep(1);
// 查看isInterrupted()(不清除标记)
System.out.println("主线程查看:isInterrupted()=" + thread.isInterrupted());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

核心区别(王二抄在小本本上)

“简单说,interrupted () 是‘看一眼,撕标签’,isInterrupted () 是‘看一眼,留标签’,” 哇哥敲着桌子,“面试时要是把这俩弄混,面试官立马知道你没真用过。”
🔔 示例 3:终止阻塞中的线程(IO 阻塞的特殊处理)
王二的下载线程卡在 IO 读取上,哇哥教他用 interrupt + 关闭流的方式终止:
package cn.tcmeta.interrupt;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.TimeUnit;
/**
* @author: laoren
* @description: 终止IO阻塞的线程:interrupt+关闭流
* @version: 1.0.0
*/
public class InterruptIOThreadSample {
private static class DownloadThread extends Thread {
private final String url;
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
private InputStream in; // 保存输入流,用于手动关闭
public DownloadThread(String url) {
super("Download-Thread");
this.url = url;
}
@Override
public void run() {
try {
URLConnection conn = new URL(url).openConnection();
in = conn.getInputStream();
byte[] buffer = new byte[1024];
int len;
// 循环读取,同时检查中断标记
while ((len = in.read(buffer)) != -1 && !Thread.currentThread().isInterrupted()) {
outputStream.write(buffer, 0, len);
TimeUnit.MILLISECONDS.sleep(10);
}
if (Thread.currentThread().isInterrupted()) {
System.out.println("线程检测到中断,主动终止下载");
} else {
System.out.println("下载完成:文件大小=" + outputStream.size() + "字节");
}
} catch (IOException e) {
System.out.println("IO流被关闭,下载终止");
} catch (InterruptedException e) {
System.out.println("线程休眠时被中断,准备终止");
Thread.currentThread().interrupt(); // 重置标记
} finally {
// 终止前关闭流,释放资源
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public byte[] getContent() {
return outputStream.toByteArray();
}
}
static void main() throws InterruptedException {
DownloadThread downloadThread = new DownloadThread("https://www.baidu.com");
downloadThread.start();
// 200ms后中断线程
TimeUnit.MILLISECONDS.sleep(200);
System.out.println("主线程:中断下载线程并关闭流");
downloadThread.interrupt();
// 等待线程终止
downloadThread.join();
System.out.println("最终文件大小:" + downloadThread.getContent().length + "字节");
}
}
- 执行结果
主线程:中断下载线程并关闭流
线程检测到中断,主动终止下载
最终文件大小:0字节
Process finished with exit code 0
“IO 阻塞比较特殊,interrupt () 只能贴标记,没法直接唤醒,” 哇哥解释,“所以得手动检查标记,关闭流 —— 就像工人卡在搬货的仓库里,光贴通知没用,得把仓库门打开(关流),他才能出来。”
五、面试必问:Thread.interrupt () 核心题(附答案)
✔️ 面试题 1:Thread.interrupt () 的底层原理是什么?它会直接杀死线程吗?
用 “工厂停工” 的例子答,面试官一听就懂:
底层原理:
- 调用 interrupt () 时,JVM 先给线程设置 “中断标记”(interruptedStatus=true);
若线程处于 sleep/wait/join 等阻塞状态,JVM 会唤醒线程,清除中断标记,抛出 InterruptedException; - 底层通过 native 方法 interrupt0 () 实现,最终调用操作系统的线程中断机制(如 Linux 的 pthread_kill)。
不会直接杀死线程:
- interrupt 只是 “中断通知”,线程是否终止,取决于是否检查中断标记、处理中断异常 —— 就像递停工通知,工人不看通知,照样干活。
👉 面试题 2:InterruptedException 抛出后,中断标记会怎样?为什么?
- 标记会被清除(置为 false);
- 原因:JVM 的设计初衷是让线程有 “重新开始” 的机会 —— 异常表示线程 “被打断”,但标记清除后,线程可以选择继续运行,而不是被标记绑定死;若需要保留中断状态,需在 catch 块中调用 Thread.currentThread ().interrupt () 重置标记。
📢 面试题 3:Thread.interrupted () 和 Thread.isInterrupted () 的区别?
- 核心是 “是否清除标记”+“是否静态”:
- Thread.interrupted():静态方法,查看当前线程的中断标记,读取后立即清除标记(置为 false);
- Thread.isInterrupted():实例方法,查看指定线程的中断标记,只读取不清除;
举例:线程 A 调用 interrupt () 后,isInterrupted () 返回 true;调用 interrupted () 后返回 true,再次调用则返回 false(标记已清)。
六、总结:interrupt 核心心法(王二编的顺口溜)
王二把核心知识点编成顺口溜,贴在键盘上,字丑但好记:
- interrupt 不杀线程,只贴标记加提醒;
- 阻塞线程抛异常,标记清零要记清;
- 运行线程查标记,优雅终止才正经;
- interrupted 清标记,isInterrupted 只看行;
- IO 阻塞特殊办,关流中断两并行。
王二用优化后的代码替换了之前的下载程序,调用 interrupt () 后,线程检查标记,关闭 IO 流,优雅终止,再也没出现文件残缺的情况。领导看了测试报告,拍着他的肩膀说:“这代码,终于不像野路子了。”
王二拿着报告去找哇哥,脸上笑开了花。哇哥剥了颗花生,慢悠悠说出那句收尾的话:
哇哥说:“技术这东西,最忌望文生义。你把 interrupt 当‘杀线程’的刀,自然会被它反咬;你把它当‘递通知’的信,懂它的脾气,顺它的逻辑,它才会听你的话。这世上本没有难用的 API,只有不肯花心思懂它的人 —— 就像待人,你懂他,他才懂你。”


572

被折叠的 条评论
为什么被折叠?



