Java中的线程中断及其处理

线程中断


阅读原文 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();

六、注意事项

  1. 中断不会强制线程停止,它只是一个协作信号
  2. 某些阻塞方法(如 sleep())会抛出 InterruptedException,你需要捕获并决定是否继续执行;
  3. 响应中断后是否重新设置中断标志位,取决于业务需求(通常会 Thread.currentThread().interrupt());
  4. 守护线程如果不及时响应中断,可能导致程序退出时资源无法释放。

七、实战中的典型用法场景

  • 停止工作线程(如消费者线程、定时任务);
  • 优雅关闭线程池;
  • 可中断的阻塞操作(网络 I/O、数据库、队列等);
  • 实现超时机制。

LockSupport


LockSupport.park() 会因线程中断而返回(被唤醒),但不会抛出 InterruptedException 异常
如果你希望知道是否是因为中断被唤醒的,需要手动检查线程的中断状态。

LockSupport.park(); // 阻塞当前线程

这个方法会让当前线程无限期阻塞,直到以下几种情况之一发生:

  1. LockSupport.unpark(thread) 显式唤醒;
  2. 被中断;
  3. 系统出现“虚假唤醒”(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()` 被调用,或
* 指定超时时间到达

这样线程就从运行状态变成了“等待态”,调度器会去调度其他线程。

注意点

  1. 如果 nanos 很短(几十微秒),JVM 可能仍然使用自旋短等待
  2. 如果 nanos 较长(如 100 微秒以上),JVM 直接调用内核挂起线程
  3. 等待期间,线程被从 CPU 核心踢出,进入“等待状态”,完全不占用 CPU 资源

实战建议

  • 使用 park() 后应手动检测中断状态
LockSupport.park();
if (Thread.currentThread().isInterrupted()) {
    // 响应中断
}
  • 若你需要 InterruptedException 的机制,请使用 sleep()wait() 等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

坚定的小辣鸡在努力

你的支持是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值