杰瑞科技汇

Java线程interrupt机制如何正确使用?

是什么?—— 中断的本质

一个最核心、最需要澄清的观念是:

Java线程interrupt机制如何正确使用?-图1
(图片来源网络,侵删)

interrupt() 方法并不会直接“杀死”或强制停止一个正在运行的线程。

Java 中没有一种安全、立即的方法可以强制停止一个线程,强制停止(如已废弃的 stop() 方法)会导致线程在任意点被中断,从而可能破坏对象的不变性状态,导致程序数据不一致。

interrupt() 的本质是:设置一个中断状态位,并向线程发送一个“请求中断”的信号。 它是一种协作机制,需要被中断的线程配合才能完成中断操作。

一个线程是否被中断,完全取决于它自己如何响应这个信号。

Java线程interrupt机制如何正确使用?-图2
(图片来源网络,侵删)

中断状态的三个关键方法

Java 中与中断状态相关的有三个核心方法,都定义在 Thread 类中:

方法 描述
void interrupt() 中断线程
1. 如果线程处于阻塞状态(例如正在调用 sleep(), wait(), join()),它会立即抛出 InterruptedException 异常,并清除中断状态
2. 如果线程处于运行/非阻塞状态,该方法只是简单地设置线程的中断状态为 true,线程不会立即停止。
boolean isInterrupted() 检查中断状态
这是一个实例方法,它会查询线程的中断状态,但不会改变这个状态,如果线程被中断,返回 true,否则返回 false
static boolean interrupted() 检查当前线程的中断状态,并清除它
这是一个静态方法,它会查询当前正在执行线程的中断状态,然后无论之前是何状态,都将其中断状态清除(设为 false,注意,它总是作用于当前线程

一个重要的陷阱: isInterrupted()interrupted() 的区别是初学者最容易混淆的地方,简单记:

  • thread.isInterrupted(): 问“线程 A 你被中断了吗?” -> 线程 A 回答“是/否”,自己状态不变。
  • Thread.interrupted(): 问“我(当前线程)被中断了吗?” -> 当前线程回答“是/否”,然后立刻擦掉自己的记录(状态设为 false)。

怎么做?—— 三种处理中断的场景

根据线程的状态,处理中断的方式分为三种。

线程处于阻塞状态

这是最简单的情况,当线程调用会抛出 InterruptedException 的方法时(如 Thread.sleep(), Object.wait(), Thread.join()),如果它在阻塞期间被中断,JVM 会做两件事:

Java线程interrupt机制如何正确使用?-图3
(图片来源网络,侵删)
  1. 抛出 InterruptedException 异常。
  2. 清除该线程的中断状态。

示例代码:

public class BlockedThread implements Runnable {
    @Override
    public void run() {
        try {
            // 线程会在这里阻塞 5 秒
            System.out.println("BlockedThread: 我要开始睡觉了...");
            Thread.sleep(5000);
            System.out.println("BlockedThread: 我睡醒了。");
        } catch (InterruptedException e) {
            // 捕获到异常,说明在睡眠期间被中断了
            System.out.println("BlockedThread: 哎呀,我被吵醒了!(InterruptedException)");
            // 在这里可以决定是退出线程还是继续执行
            // e.printStackTrace();
        }
        System.out.println("BlockedThread: 线程结束。");
    }
}
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new BlockedThread());
        t.start();
        // 主线程等待 1 秒后,去中断 t 线程
        Thread.sleep(1000);
        System.out.println("Main: 我要去中断 t 线程了。");
        t.interrupt(); // t 线程正在 sleep(),会立即被中断
    }
}

输出结果:

BlockedThread: 我要开始睡觉了...
Main: 我要去中断 t 线程了。
BlockedThread: 哎呀,我被吵醒了!(InterruptedException)
BlockedThread: 线程结束。

可以看到,sleep() 被提前终止,异常被抛出。


线程处于运行状态(非阻塞)

这是最常见也最需要手动处理的情况,当线程正在执行自己的任务(例如一个 for 循环)时,你调用了 interrupt(),它只是设置了一个标志位,线程需要主动、定期地检查这个标志位,以决定是否应该停止工作。

最佳实践:使用 isInterrupted() 检查

public class RunningThread implements Runnable {
    @Override
    public void run() {
        try {
            // 模拟一个长时间运行的任务
            for (int i = 0; i < 100000; i++) {
                // 1. 在循环中定期检查中断状态
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("RunningThread: 收到中断信号,准备退出循环。");
                    break; // 优雅地退出
                }
                // 模拟一些工作
                doSomeWork();
            }
            System.out.println("RunningThread: 线程正常结束。");
        } finally {
            // 清理资源
            System.out.println("RunningThread: 清理资源中...");
        }
    }
    private void doSomeWork() {
        // 模拟耗时操作
    }
}
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new RunningThread());
        t.start();
        // 主线程等待 2 秒后,去中断 t 线程
        Thread.sleep(2000);
        System.out.println("Main: 我要去中断 t 线程了。");
        t.interrupt();
    }
}

输出结果:

Main: 我要去中断 t 线程了。
RunningThread: 收到中断信号,准备退出循环。
RunningThread: 线程正常结束。
RunningThread: 清理资源中...

这种方式是最推荐的,因为它不依赖于异常,代码逻辑清晰,能明确地响应中断请求。


线程在调用可中断方法前,中断状态已被设置

这是一个非常微妙但重要的场景,假设一个线程的中断状态已经被设置为 true,然后它去调用一个会抛出 InterruptedException 的方法(sleep())。

会发生什么? sleep() 方法会检查当前的中断状态,如果发现状态已经是 true,它不会抛出异常,而是直接返回,它会保持中断状态为 true

示例代码:

public class ScenarioThreeThread implements Runnable {
    @Override
    public void run() {
        // 假设线程的中断状态已经被外部设置为 true
        // 我们通过 Thread.interrupted() 来模拟,它会清除状态
        // 但为了演示,我们先手动设置
        // Thread.currentThread().interrupt(); // 手动设置中断状态为 true
        System.out.println("ScenarioThreeThread: 进入 run(),中断状态是 " + Thread.currentThread().isInterrupted());
        try {
            System.out.println("ScenarioThreeThread: 准备调用 sleep()...");
            Thread.sleep(1000); // 检查到中断状态为 true,直接返回,不抛异常
            System.out.println("ScenarioThreeThread: sleep() 调用完成。"); // 这行代码不会执行
        } catch (InterruptedException e) {
            // 这里不会执行,因为 sleep() 没有抛出异常
            System.out.println("ScenarioThreeThread: 捕获到 InterruptedException!");
        }
        System.out.println("ScenarioThreeThread: sleep() 之后,中断状态是 " + Thread.currentThread().isInterrupted());
    }
}
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new ScenarioThreeThread());
        t.start();
        // 在 t 线程有机会调用 sleep() 之前,先中断它
        t.interrupt(); 
    }
}

输出结果:

ScenarioThreeThread: 进入 run(),中断状态是 true
ScenarioThreeThread: 准备调用 sleep()...
ScenarioThreeThread: sleep() 之后,中断状态是 true

如果你的代码在调用 sleep(), wait() 等方法之前,不确定中断状态是什么,那么最好先处理掉这个中断信号,或者至少意识到它存在,否则,你的代码可能会因为 sleep() 的“静默返回”而产生不符合预期的行为。


最佳实践与总结

  1. 永远不要使用 Thread.stop():它已废弃,极不安全。

  2. 响应中断是线程的责任:调用 interrupt() 只是发出一个请求,被中断的线程必须在合适的时机检查中断状态并做出响应(通常是退出任务)。

  3. 优先使用 isInterrupted():在非阻塞代码中,通过 while (!Thread.currentThread().isInterrupted())if (Thread.currentThread().isInterrupted()) 来检查中断状态,这是最清晰、最健壮的方式。

  4. 正确处理 InterruptedException

    • 传播异常:如果你的方法本身也是一个可中断操作(比如你在一个 run() 方法里调用了 sleep()),那么最简单的方法就是将 InterruptedException 抛出去,让调用者去处理。
    • 恢复中断状态:如果你捕获了 InterruptedException,但又不能或不想抛出它,你应该在 catch 块中恢复中断状态,以便上层调用者也能感知到中断请求。
      try {
          Thread.sleep(1000);
      } catch (InterruptedException e) {
          // 恢复中断状态,以便外层逻辑也能感知
          Thread.currentThread().interrupt(); 
          System.out.println("我被中断了,并恢复了这个状态。");
          // 或者,如果你只是想记录日志,也可以选择恢复
          // e.printStackTrace();
          // Thread.currentThread().interrupt();
      }
  5. 理解 interrupted() 的陷阱:它是一个静态方法,会清除当前线程的中断状态,除非你确实需要这个“检查并清除”的行为,否则在绝大多数情况下,你应该使用实例方法 isInterrupted()

  6. 中断不等于停止:中断是一个请求,线程可以忽略它(尽管不推荐),或者处理完当前任务后再退出,这给了你极大的灵活性来设计健壮的并发代码。

希望这份详细的解释能帮助你彻底理解 Java 的线程中断机制!

分享:
扫描分享到社交APP
上一篇
下一篇