跳转到内容
唯一赫兹
返回

Java 中断机制

引言

Java 中的「中断」并不是强制终止一个线程,而是一种 协作式的通知机制。调用 interrupt() 只是告诉目标线程「该停了」,具体停不停,由线程自己决定。

每个线程内部维护一个 boolean 值作为「中断标志位」,默认为 false。当在外部调用 interrupt() 时,会修改目标线程的中断标志位,并根据线程当前所处的状态做出不同响应。

中断相关 API

JDK 提供了三个与中断相关的 API:

API作用
thread.interrupt()中断目标线程,将中断标志位置为 true
thread.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 也支持超时)只作用于当前线程

小结



下一篇
【JVM 4】CMS | G1 | ZGC