如何在Java中处理InterruptedException

2023/06/07

1. 概述

在本文中,我们将探讨Java的InterruptedException。首先,我们简要回顾一下线程的生命周期。接下来,我们会看到在多线程应用程序中可能会出现的中断异常。最后,我们将了解如何处理此异常。

2. 多线程基础

在介绍中断之前,让我们回顾一下多线程。多线程是同时执行两个或多个线程的进程。Java应用程序从一个与main()方法关联的线程开始,称为主(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()方法,以及BlockingQueue的put()和take()方法等等。

3.3 Thread中的中断方法

让我们快速了解一下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()来检查中断时,中断状态被清除

public class Thread implements Runnable {
    private volatile boolean interrupted;
}

为了响应中断请求,我们必须处理InterruptedException。

4. 如何处理InterruptedException

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

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

有一些处理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 cn.tuyucheng.taketoday.concurrent.interrupt.InterruptExample.propagateException(InterruptExample.java:16)
    at cn.tuyucheng.taketoday.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
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:

@Test
void whenHandleWithCustomException_thenReturnsTrue() throws CustomInterruptedException {
    assertTrue(InterruptExample.handleWithCustomException());
}

5. 总结

在本教程中,我们看到了处理InterruptedException的不同方法。如果处理得当,我们可以平衡应用程序的响应性和健壮性。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章