一、简介
在本教程中,我们将探讨 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() 方法以及 BlockingQueue 的 put() 和 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 上找到。