一、简介

在本教程中,我们将探讨 Java 的 InterruptedException 。首先,我们将通过插图快速了解线程的生命周期。接下来,我们将了解在多线程应用程序中工作如何可能导致 InterruptedException 。最后,我们将了解如何处理此异常。

2. 多线程基础知识

在讨论中断之前,我们先回顾一下多线程。多线程是同时执行两个或多个线程的过程。 Java 应用程序从一个与 main() 方法关联的线程(称为主线程)开始。然后这个主线程可以启动其他线程。

线程是轻量级的,这意味着它们在相同的内存空间中运行。因此,他们之间可以轻松地进行交流。我们先看一下线程的生命周期

一旦我们创建了一个新线程,它就处于 NEW 状态。它一直保持这种状态,直到程序使用 start() 方法启动线程。

在线程上调用 start() 方法会将其置于 RUNNABLE 状态。处于此状态的线程要么正在运行,要么准备运行。

当一个线程正在等待监视器锁并尝试访问被其他线程锁定的代码时,它会进入 BLOCKED 状态。

线程可以通过各种事件进入 WAITING 状态,例如调用 wait() 方法。在此状态下,一个线程正在等待来自另一个线程的信号。

当线程完成执行或异常终止时,它将进入 TERMINATED 状态。 线程是可以被中断的,当线程被中断时,会抛出 InterruptedException

在接下来的部分中,我们将详细了解 InterruptedException 并了解如何响应它。

3. 什么是 InterruptedException

当线程在等待、休眠或以其他方式占用时被中断时,将引发 InterruptedException 。换句话说,某些代码调用了我们线程上的 interrupt() 方法。这是一个受检查的异常,Java 中的许多阻塞操作都可以抛出它。

3.1.中断

**中断系统的目的是提供一个定义良好的框架,允许线程中断其他线程中的任务(可能是耗时的任务)。 ** 考虑中断的一个好方法是,它实际上并不中断正在运行的线程 - 它只是请求线程在下一个方便的机会中断自己。

3.2.阻塞和中断方法

线程可能因多种原因而阻塞:等待从 Thread.sleep () 中唤醒、 等待获取锁、等待 I/O 完成或等待另一个线程中的计算结果等。

InterruptedException 通常由所有阻塞方法引发,以便可以对其进行处理并执行纠正操作。 Java 中有多种方法会抛出 InterruptedException 。其中包括 Thread.sleep()Thread.join()Object 类的 wait() 方法以及 BlockingQueueput()take() 方法等。

3.3.线程中的中断方法

让我们快速浏览一下 Thread 类处理中断的一些关键方法:

public void interrupt() { ... }
public boolean isInterrupted() { ... }
public static boolean interrupted() { ... }

Thread 提供了 interrupt() 方法来中断线程,要查询线程是否被中断,我们可以使用 isInterrupted() 方法 。有时候,我们可能希望测试当前线程是否被中断,如果是,则立即抛出此异常。在这里,我们可以使用 interrupted() 方法:

if (Thread.interrupted()) {
    throw new InterruptedException();
}

3.4.中断状态标志

中断机制是使用称为中断状态的标志来实现的。 每个线程都有一个 布尔 属性来表示其中断状态。调用 Thread.interrupt() 设置此标志。 * 当线程通过调用 静态 方法 Thread.interrupted() 检查中断时,中断状态被清除。 *

为了响应中断请求,我们必须处理 InterruptedException。 我们将在下一节中了解如何做到这一点。

4. 如何处理 InterruptedException

需要注意的是,线程调度依赖于 JVM。这是很自然的,因为 JVM 是一个虚拟机,需要本机操作系统资源来支持多线程。因此,我们不能保证我们的线程永远不会被中断。

中断是向线程发出的指示,表明它应该停止正在执行的操作并执行其他操作。更具体地说,如果我们正在编写一些将由 执行程序 或其他线程管理机制执行的代码,我们需要确保我们的代码能够及时响应中断。否则,我们的应用程序可能会导致死锁

处理 InterruptedException 的实用策略很少。让我们来看看它们。

4.1.传播 InterruptedException

我们可以允许 InterruptedException 向上传播到调用堆栈,例如, 依次向每个方法添加 throws 子句,并让调用者决定如何处理中断 。这可能涉及我们不捕获异常或捕获并重新抛出异常。让我们尝试通过一个例子来实现这一点:

public static void propagateException() throws InterruptedException {
    Thread.sleep(1000);
    Thread.currentThread().interrupt();
    if (Thread.interrupted()) {
        throw new InterruptedException();
    }
}

在这里,我们检查线程是否被中断,如果是,我们抛出 InterruptedException 。现在,让我们调用 propagateException() 方法:

public static void main(String... args) throws InterruptedException {
    propagateException();
}

当我们尝试运行这段代码时,我们将收到一个带有堆栈跟踪的 InterruptedException

Exception in thread "main" java.lang.InterruptedException
    at com.baeldung.concurrent.interrupt.InterruptExample.propagateException(InterruptExample.java:16)
    at com.baeldung.concurrent.interrupt.InterruptExample.main(InterruptExample.java:7)

虽然这是响应异常的最明智的方式,但有时我们不能抛出它 - 例如,当我们的代码是 Runnable 的一部分时。在这种情况下,我们必须抓住它,恢复状态。我们将在下一节中了解如何处理这种情况。

4.2.恢复中断

在某些情况下,我们无法传播 InterruptedException。 例如,假设我们的任务是由 Runnable 定义的或重写不抛出任何已检查异常的方法。在这种情况下,我们可以保留中断。执行此操作的标准方法是恢复中断状态。

我们可以再次调用 Interrupt() 方法(它将把标志设置回 true ),以便调用堆栈上方的代码可以看到发出了中断。 例如,让我们中断一个线程并尝试访问其中断状态:

public class InterruptExample extends Thread {
    public static Boolean restoreTheState() {
        InterruptExample thread1 = new InterruptExample();
        thread1.start();
        thread1.interrupt();
        return thread1.isInterrupted();
    }
}

下面是处理该中断并恢复中断状态的 run() 方法:

public void run() {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();  //set the flag back to true
 } }

最后我们来测试一下状态:

assertTrue(InterruptExample.restoreTheState());

尽管 Java 异常涵盖了所有异常情况和条件,但我们可能希望抛出代码和业务逻辑特有的特定自定义异常。在这里,我们可以创建自定义异常来处理中断。我们将在下一节中看到它。

4.3.自定义异常处理

自定义异常提供了添加不属于标准 Java 异常的属性和方法的灵活性。因此, 根据情况以自定义方式处理中断是完全有效的

我们可以完成额外的工作,让应用程序能够优雅地处理中断请求。例如,当一个线程正在睡眠或等待 I/O 操作时,它收到中断,我们可以在终止线程之前关闭任何资源。

让我们创建一个名为 CustomInterruptedException 的自定义检查异常:

public class CustomInterruptedException extends Exception {
    CustomInterruptedException(String message) {
        super(message);
    }
}

当线程中断时,我们可以抛出 CustomInterruptedException

public static void throwCustomException() throws Exception {
    Thread.sleep(1000);
    Thread.currentThread().interrupt();
    if (Thread.interrupted()) {
        throw new CustomInterruptedException("This thread was interrupted");
    }
}

让我们看看如何检查异常是否抛出并带有正确的消息:

@Test
 public void whenThrowCustomException_thenContainsExpectedMessage() {
    Exception exception = assertThrows(CustomInterruptedException.class, () -> InterruptExample.throwCustomException());
    String expectedMessage = "This thread was interrupted";
    String actualMessage = exception.getMessage();

    assertTrue(actualMessage.contains(expectedMessage));
}

同样,我们可以处理异常并恢复中断状态

public static Boolean handleWithCustomException() throws CustomInterruptedException{
    try {
        Thread.sleep(1000);
        Thread.currentThread().interrupt();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new CustomInterruptedException("This thread was interrupted...");
    }
    return Thread.currentThread().isInterrupted();
}

我们可以通过检查中断状态来测试代码以确保它返回 true

assertTrue(InterruptExample.handleWithCustomException());

5. 结论

在本教程中,我们看到了处理 InterruptedException 的 不同方法。如果我们处理得当,我们就可以平衡应用程序的响应能力和健壮性。而且,与往常一样,这里使用的代码片段可以在 GitHub 上找到。