引言
Java 中的「中断」并不是强制终止一个线程,而是一种 协作式的通知机制。调用 interrupt() 只是告诉目标线程「该停了」,具体停不停,由线程自己决定。
每个线程内部维护一个 boolean 值作为「中断标志位」,默认为 false。当在外部调用 interrupt() 时,会修改目标线程的中断标志位,并根据线程当前所处的状态做出不同响应。
中断相关 API
JDK 提供了三个与中断相关的 API:
| API | 作用 |
|---|---|
thread.interrupt() | 中断目标线程,将中断标志位置为 true |
thread.isInterrupted() | 返回 指定线程 的中断标志位,不改变标志位 |
Thread.interrupted() | 返回 当前线程 的中断标志位,同时 重置 中断标志位 |
isInterrupted()是成员方法,作用于调用它的那个Thread对象,只读不写interrupted()是静态方法,作用于「当前正在执行的线程」,读取后会把中断标志位清掉,相当于「读后即焚」
interrupt() 的行为
对某个 Thread 调用 interrupt() 时,行为取决于该线程当前所处的状态:
| 线程状态 | interrupt() 的行为 |
|---|---|
RUNNABLE(运行中) | 不会抛出异常,只会修改 Thread 内部的中断状态值 |
BLOCKED / WAITING | 通过 sleep()、wait()、join() 进入该状态 → 抛出中断异常 |
BLOCKED / WAITING | 通过 LockSupport.park() 方法进入该状态 → 修改中断状态值并返回 |
RUNNABLE:仅设置标志位
线程处于 RUNNABLE,即正常执行代码(或已就绪等待 CPU),此时调用 interrupt() 不会抛出异常,只是把中断标志位设为 true。线程内部可以轮询 isInterrupted() 来决定是否要停止自己:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
// 轮询中断标志位,检测到中断后主动退出
while (!Thread.currentThread().isInterrupted()) {
// 执行任务...
}
System.out.println("检测到中断,线程退出");
});
t.start();
Thread.sleep(100);
t.interrupt(); // 只设置中断标志位,线程不会立即停止
}Main.java
BLOCKED / WAITING:抛异常或直接返回
当线程因为调用阻塞方法而进入 BLOCKED/WAITING 状态时,中断的响应方式又分两种:
1. sleep()/wait()/join() 进入的状态
这些方法声明抛出 InterruptedException,一旦线程在等待期间被中断,会立即唤醒该线程并抛出异常,同时 自动清除中断标志位(重置为 false):
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
Thread.sleep(10_000); // 睡眠期间被中断
} catch (InterruptedException e) {
// 抛出异常的同时,中断标志位已被清为 false
System.out.println("被中断,标志位 = " + Thread.currentThread().isInterrupted());
}
});
t.start();
Thread.sleep(100);
t.interrupt(); // 让睡眠中的线程抛出 InterruptedException
}Main.java
2. LockSupport.park() 进入的状态
LockSupport.park() 也用于让线程阻塞,但它 不抛异常。线程在 park 期间被中断时,会修改中断状态值(置为 true)并立即中止阻塞,因此需要显式地检查中断标志位:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
LockSupport.park(); // 阻塞期间被中断,直接返回,不抛异常
System.out.println("park 返回,中断标志位 = " + Thread.currentThread().isInterrupted());
});
t.start();
Thread.sleep(100);
t.interrupt();
}Main.java
重新设置中断标志位
由于 sleep()/wait()/join() 抛出异常时会 清除 中断标志位,如果当前线程自身也需要感知这次中断(比如想把中断继续向上传播给调用方),需要在 catch 块中重新设置:
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 异常抛出后标志位已被清空,重新置为 true,让外层循环感知到
Thread.currentThread().interrupt();
break;
}
}
});
}Main.java
关于协作机制
interrupt 会向目标线程发送一个中断信号,让目标线程自行决定行为,而不是强制中断,目的是避免死锁。
Java 1.2 起就弃用了 suspend stop 等 API,原因就是这些 API 会强制中断/挂起线程,导致锁还没释放或某些操作才进行到一半就中止了,很容易出现并发问题。后来的 API 要么是只允许操作当前线程,要么像 interrupt 一样只发信号。
| API | 机制 | 优点 |
|---|---|---|
interrupt | 将目标线程的中断标志位置为 true | 通知目标线程 |
wait / await | 释放当前线程持有的锁并挂起 | 1. 只作用于当前线程 2. 挂起前会释放锁 |
join | 让当前线程挂起直到目标线程结束 | 只作用于当前线程 |
sleep | 让当前线程挂起一段时间 | 只作用于当前线程 |
park | 让当前线程挂起(park 也支持超时) | 只作用于当前线程 |
小结
interrupt()是「通知」,不是「强制终止」,最终是否退出由线程自己决定- 线程处于
RUNNABLE时,中断只修改标志位,不抛异常 - 线程通过
sleep()/wait()/join()进入阻塞时,中断会抛InterruptedException并清除标志位 - 线程通过
LockSupport.park()进入阻塞时,中断会修改标志位并返回,不抛异常 isInterrupted()读标志位不重置,interrupted()读标志位并重置