线程中断
阅读原文 https://www.xiaozaoshu.top/articles/interruption
一、什么是线程中断?
线程中断(interruption)是一种 协作式 的线程取消机制。
Java 不强制终止线程,而是通过设置中断标志位来告诉线程:“你该停了”。
线程响应中断通常是主动查询中断状态,然后做出处理(如退出循环、清理资源、停止运行等)。
Thread.currentThread().isInterrupted()
是什么?
这是 Thread
类中的一个实例方法,表示:
检查当前线程是否被中断(即中断标志是否被设置为 true),但不会清除该标志。
if (Thread.currentThread().isInterrupted()) {
// 中断了,准备退出
}
在 Java 中,一个线程的中断(interrupt)并不会自动发生,必须由其他线程或框架代码主动调用 thread.interrupt()
。
二、常见会触发线程中断的情况
1. 你自己显式调用 thread.interrupt()
这是最常见的方式,尤其在:
- 优雅关闭线程
- 停止阻塞的线程
- 线程池 shutdown 时
Thread t = new Thread(...);
t.start();
...
t.interrupt(); // 通知线程“你该退出了”
2. 线程池(如 ExecutorService
)调用 shutdownNow()
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.shutdownNow(); // 会中断正在执行的线程
✔️
shutdownNow()
会调用所有工作线程的interrupt()
❌shutdown()
不会中断线程,只是阻止新任务提交
3. 主线程希望提前结束某些子线程
例如在应用退出时,调用 workerThread.interrupt()
通知子线程尽快退出。
4. 使用 Future.get()
被取消
Future<?> f = executor.submit(task);
f.cancel(true); // 会中断对应的线程(如果还在运行)
5. 库/框架内部自动发起中断
例如某些任务调度框架、定时器、或者操作系统信号处理逻辑可能在内部调用 interrupt()
,比如:
java.util.Timer
超时Thread.join()
等待超时ForkJoinPool
超时等待
不过这些需要你明确调用超时方法,或者注册中断逻辑。
6. 外部信号或 JVM 关闭钩子中断守护线程
有时你会在 JVM 关闭钩子中,调用某些守护线程的 interrupt()
让它尽快退出:
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
consumerThread.interrupt(); // 通知守护线程退出
}));
什么时候线程不会自动中断?
线程不会自己被中断,也不会因为运行太久、阻塞太久而自动触发中断。
你必须依赖显式的 interrupt() 调用 或者由框架自动帮你调用。
三、常见的中断方法对比
方法 | 作用 | 是否清除中断状态 |
---|---|---|
Thread.currentThread().isInterrupted() | 查看当前线程是否被中断 | ❌ 不会清除 |
thread.isInterrupted() | 查看指定线程是否被中断 | ❌ 不会清除 |
Thread.interrupted() | 查看当前线程是否被中断 | ✅ 会清除中断标志位 |
thread.interrupt() | 设置目标线程的中断标志 | 不会立即终止,只设置标志位 |
四、线程如何响应中断?
线程需要自己处理中断,通常有两种方式:
1. 主动轮询中断状态
while (!Thread.currentThread().isInterrupted()) {
// 执行业务逻辑
}
2. 被阻塞操作(如 sleep()
、wait()
、join()
)中断
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// 被中断了
Thread.currentThread().interrupt(); // 重新设置中断状态(推荐)
return;
}
五、使用中断的正确模式
public class MyTask implements Runnable {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// 执行任务
}
} catch (Exception e) {
// 异常处理
} finally {
// 资源清理
}
}
}
外部中断方式:
Thread t = new Thread(new MyTask());
t.start();
// 触发中断
t.interrupt();
六、注意事项
- 中断不会强制线程停止,它只是一个协作信号;
- 某些阻塞方法(如
sleep()
)会抛出InterruptedException
,你需要捕获并决定是否继续执行; - 响应中断后是否重新设置中断标志位,取决于业务需求(通常会
Thread.currentThread().interrupt()
); - 守护线程如果不及时响应中断,可能导致程序退出时资源无法释放。
七、实战中的典型用法场景
- 停止工作线程(如消费者线程、定时任务);
- 优雅关闭线程池;
- 可中断的阻塞操作(网络 I/O、数据库、队列等);
- 实现超时机制。
LockSupport
LockSupport.park()
会因线程中断而返回(被唤醒),但不会抛出InterruptedException
异常。
如果你希望知道是否是因为中断被唤醒的,需要手动检查线程的中断状态。
LockSupport.park(); // 阻塞当前线程
这个方法会让当前线程无限期阻塞,直到以下几种情况之一发生:
- 被
LockSupport.unpark(thread)
显式唤醒; - 被中断;
- 系统出现“虚假唤醒”(spurious wakeup)。
被中断时,LockSupport#park 的行为:
park()
会立即返回;- 不会抛异常;
- 当前线程的中断状态仍然是 true(不会被清除);
- 如果你不处理,会影响后续如
park()
或sleep()
的行为。
示例代码
public class ParkInterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("Thread: parking...");
LockSupport.park();
System.out.println("Thread: unparked, interrupted = " + Thread.currentThread().isInterrupted());
});
thread.start();
Thread.sleep(1000); // 确保线程先 park
thread.interrupt(); // 中断线程
}
}
输出示例:
Thread: parking...
Thread: unparked, interrupted = true
和 Object.wait()
/ Thread.sleep()
的比较:
方法 | 响应中断 | 抛出异常 | 清除中断标志 |
---|---|---|---|
Thread.sleep() | ✅ | ✅ InterruptedException | ✅ 是 |
Object.wait() | ✅ | ✅ InterruptedException | ✅ 是 |
LockSupport.park() | ✅ | ❌ 不抛异常 | ❌ 不清除 |
LockSupport.parkNanos()
为什么不像“自旋”那样占用 CPU,却能“高效等待”?
LockSupport.parkNanos()
底层通过 系统调用(如 Linux 的 futex) 将线程挂起(阻塞),
在指定时间或被唤醒后恢复,不进行“主动轮询”或 CPU 忙等,
所以 它本质上是非 CPU 占用式的“睡眠等待”机制。
- 关键概念:线程的“挂起” vs “自旋”
等待方式 | 是否占 CPU | 描述 |
---|---|---|
自旋(spin) | ✅ 是 | while 循环检查状态,CPU 忙等 |
parkNanos | ❌ 否 | 调用系统阻塞机制,让出 CPU |
Thread.sleep() | ❌ 否 | 同样调用 OS 阻塞机制 |
LockSupport.parkNanos()
的底层实现(HotSpot VM)
在 OpenJDK/HotSpot 中,LockSupport.parkNanos()
是基于 Unsafe.park(...)
实现的。
public static void parkNanos(long nanos) {
Unsafe.getUnsafe().park(false, nanos);
}
在 Linux 下,Unsafe.park(...)
会使用:
* `futex`(Fast Userspace Mutex)系统调用
* 或者 `pthread_cond_timedwait` / `nanosleep` 等内核挂起机制
也就是说:
线程会被内核挂起,加入等待队列,调度器让出 CPU,等待唤醒或超时。
举个例子:futex 等待模型
在 Linux 上,Unsafe.park()
会调用:
futex(addr, FUTEX_WAIT, expected_value, timeout);
作用:
当前线程挂起(阻塞)直到:
* `unpark()` 被调用,或
* 指定超时时间到达
这样线程就从运行状态变成了“等待态”,调度器会去调度其他线程。
注意点
- 如果 nanos 很短(几十微秒),JVM 可能仍然使用自旋短等待
- 如果 nanos 较长(如 100 微秒以上),JVM 直接调用内核挂起线程
- 等待期间,线程被从 CPU 核心踢出,进入“等待状态”,完全不占用 CPU 资源
实战建议
- 使用
park()
后应手动检测中断状态:
LockSupport.park();
if (Thread.currentThread().isInterrupted()) {
// 响应中断
}
- 若你需要
InterruptedException
的机制,请使用sleep()
、wait()
等。