杰瑞科技汇

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

协作式中断

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

Java线程interrupt,中断机制如何正确使用?-图1
(图片来源网络,侵删)
  • 抢占式:就像你正在打电话,突然有人直接把电话线拔了,通话被强制中断。
  • 协作式:就像你正在打电话,有人给你发了个短信说“有急事,请尽快结束通话”,你看到了短信,然后决定说“再见”并挂断电话,或者你也可以选择忽略这个短信,继续打。

在 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() 方法,

Java线程interrupt,中断机制如何正确使用?-图2
(图片来源网络,侵删)
  1. 阻塞被立即解除
  2. 抛出 InterruptedException 异常
  3. 该线程的中断状态会被自动清除(设置为 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() 检查当前线程是否被中断 (清除状态)

核心要点

  1. 中断是协作式的:被中断的线程必须自己决定如何响应。
  2. 检查中断状态:在循环或长时间任务中,使用 isInterrupted() 检查中断。
  3. 处理 InterruptedException:在捕获该异常后,要么重新设置中断状态 (thread.interrupt()),要么重新抛出它,以确保中断信号能够传递给上层代码。
  4. 避免使用 stop():使用 stop() 会导致程序状态不一致,是极其危险的操作。

通过遵循这些原则,你可以编写出健壮、可响应、可取消的并发 Java 应用程序。

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