协作式中断
首先要明确一个核心思想:Java 的中断机制是协作式的,而不是抢占式的。

- 抢占式:就像你正在打电话,突然有人直接把电话线拔了,通话被强制中断。
- 协作式:就像你正在打电话,有人给你发了个短信说“有急事,请尽快结束通话”,你看到了短信,然后决定说“再见”并挂断电话,或者你也可以选择忽略这个短信,继续打。
在 Java 中,一个线程不能直接“杀死”或强制停止另一个线程,当一个线程被中断时,它只是被设置了一个中断状态,就像收到了那个“短信”,被中断的线程需要自己检查这个状态,并决定接下来该做什么(提前结束任务、抛出异常等)。
中断相关的三个核心方法
Java 提供了三个核心方法来操作中断状态:
Thread.interrupt()
- 作用:中断目标线程。
- 行为:
- 如果目标线程正在
wait(),join(),sleep()等会导致线程阻塞的方法中,调用interrupt()会立即抛出InterruptedException异常,并且会清除线程的中断状态。 - 如果目标线程没有阻塞,
interrupt()方法只是简单地将线程的中断状态设置为true,线程需要自己通过检查中断状态来做出响应。
- 如果目标线程正在
Thread.isInterrupted()
- 作用:测试线程是否被中断。
- 行为:
- 这是一个实例方法,用于检查调用该方法的对象所代表的线程的中断状态。
- 它不会改变中断状态,也就是说,调用一次
isInterrupted(),如果状态是true,再次调用它,状态依然是true。
Thread.interrupted()
- 作用:测试当前线程是否被中断。
- 行为:
- 这是一个静态方法,用于检查正在执行此代码的当前线程的中断状态。
- 它有一个非常重要的副作用:它会清除当前线程的中断状态,如果状态是
true,调用interrupted()后,状态会被重置为false。
中断状态与阻塞方法
这是理解中断最关键的一点,当一个线程因为调用以下方法而阻塞时:
Thread.sleep(long millis)Thread.join()Object.wait()java.util.concurrent.locks.Lock.lockInterruptibly()java.nio.channels.InterruptibleChannel
如果在阻塞期间,另一个线程调用了该线程的 interrupt() 方法,

- 阻塞被立即解除。
- 抛出
InterruptedException异常。 - 该线程的中断状态会被自动清除(设置为
false)。
这意味着,如果你在 catch (InterruptedException e) 块中捕获了异常,线程的中断状态已经丢失了,如果你想在捕获异常后仍然保持中断状态,你需要手动将其重新设置:
try {
Thread.sleep(10000); // 假设这里会一直睡
} catch (InterruptedException e) {
// 1. 线程被中断,sleep 抛出异常
// 2. 中断状态已被自动清除 (变为 false)
System.out.println("睡眠被中断,正在处理...");
// 3. 重新设置中断状态,以便上层调用者也能知道
Thread.currentThread().interrupt();
// 或者直接 re-throw
// throw new RuntimeException("睡眠被中断", e);
}
最佳实践:如何正确地处理中断?
编写一个能正确响应中断的线程,通常遵循以下模式:
对于简单的循环任务
如果你的线程中有一个简单的循环,最佳实践是在每次循环迭代时检查中断状态。
public class SimpleTask implements Runnable {
@Override
public void run() {
// 不停地工作,直到被中断
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Working hard...");
try {
// 模拟工作
Thread.sleep(500);
} catch (InterruptedException e) {
// 如果在 sleep 时被中断,会抛出异常
// 并且中断状态会被清除
System.out.println("Task was interrupted during sleep.");
// 重新设置中断状态,并退出循环
Thread.currentThread().interrupt();
break; // 或者 return
}
}
System.out.println("Task finished.");
}
}
注意:在这个例子中,如果中断发生在 while 循环的条件判断中(即不在 sleep 里),isInterrupted() 会返回 true,循环优雅地结束,如果中断发生在 sleep 里,sleep 会抛出异常,我们在 catch 块中重新设置中断状态并 break。
对于可以长时间运行的任务(I/O 或复杂计算)
对于可能长时间运行的任务,仅仅依靠 isInterrupted() 检查是不够的,因为任务可能长时间卡在某个地方(如网络 I/O),最佳实践是将中断状态与抛出 InterruptedException 结合起来。
public class LongRunningTask implements Runnable {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
doSomethingThatMayBlock(); // 这个方法内部应该能响应中断
}
} catch (InterruptedException e) {
// 如果在 doSomethingThatMayBlock 内部被中断,它会抛出异常到这里
System.out.println("Task was interrupted and is now exiting.");
// 可以在这里进行清理工作
} finally {
// 清理资源
System.out.println("Cleaning up resources...");
}
System.out.println("Long-running task finished.");
}
private void doSomethingThatMayBlock() throws InterruptedException {
// 假设这是一个会阻塞的操作,比如等待某个条件
// 如果这个操作支持中断(如 ReentrantLock.lockInterruptibly()),
// 它会在被中断时抛出 InterruptedException
Thread.sleep(1000); // 用 sleep 模拟
}
}
设计可中断的阻塞方法:如果你正在编写一个可能被阻塞的方法(一个等待队列的方法),你应该让它能够响应中断,一个好方法是让你的方法在检测到中断时抛出 InterruptedException。
常见误区与错误用法
误区1:使用 stop() 方法
Thread.stop() 方法是已废弃的,因为它非常危险,它会立即释放所有锁,导致对象处于不一致的状态,可能引发数据损坏。
绝对不要使用 Thread.stop()。
误区2:捕获 InterruptedException 后什么都不做
这是一个非常糟糕的实践,因为它“吞掉”了中断信号,上层调用者完全不知道线程已经被中断了,这会破坏中断机制。
// 错误示范
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 啥也不干,假装无事发生
// 线程的中断状态已经被清除,但没有任何人知道它被中断过
}
误区3:在 catch 块中恢复中断状态,然后继续循环
在某些情况下,这可能不是你想要的,如果你只是想“重置”中断状态并继续运行,那么你需要非常清楚你的意图,中断是一个“停止”或“取消”的信号,忽略它可能会导致程序在用户期望它停止时继续运行。
// 有争议的做法,通常不推荐
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Ignoring interruption and continuing...");
Thread.currentThread().interrupt(); // 重新设置,让循环继续
}
}
| 方法 | 作用 | 是否清除中断状态 |
|---|---|---|
thread.interrupt() |
中断目标线程 | 否 (除非目标线程因阻塞而抛出 InterruptedException) |
thread.isInterrupted() |
检查目标线程是否被中断 | 否 |
Thread.interrupted() |
检查当前线程是否被中断 | 是 (清除状态) |
核心要点:
- 中断是协作式的:被中断的线程必须自己决定如何响应。
- 检查中断状态:在循环或长时间任务中,使用
isInterrupted()检查中断。 - 处理
InterruptedException:在捕获该异常后,要么重新设置中断状态 (thread.interrupt()),要么重新抛出它,以确保中断信号能够传递给上层代码。 - 避免使用
stop():使用stop()会导致程序状态不一致,是极其危险的操作。
通过遵循这些原则,你可以编写出健壮、可响应、可取消的并发 Java 应用程序。
