1.概述
线程中断是一种机制,用于向线程发出信号,告知它必须在方便时停止执行。但是,是否检查中断状态并停止则取决于正在运行的任务。
2.使用Thread.interrupt中断
首先,我们将看到如何中断线程。 当我们在Thread实例上调用 interrupt时,它将设置线程的中断状态,以便代码的其他部分可以检查并对其执行操作。
public class TestInterrupt {
public static void main(String[] args) {
TestInterrupt testInterrupt=new TestInterrupt();
testInterrupt.interruptThread();
}
public void interruptThread(){
final Runnable task = () -> {
int i = 0;
while (i < Integer.MAX_VALUE) {
i++;
}
};
final Thread thread = new Thread(task);
thread.start();
System.out.println("Is thread interrupted: " + thread.isInterrupted());
thread.interrupt();
System.out.println("Is thread interrupted: " + thread.isInterrupted());
}
}
在此示例中,我们首先创建一个Runnable任务。然后,我们 使用此任务启动一个线程。最后,我们在中断线程之前和之后观察中断状态。
运行样本打印:
Is thread interrupted: false
Is thread interrupted: true
如预期的那样,线程的中断标志在中断调用后变为true。
通常,有两个方面对中断感兴趣:执行任务的代码和拥有线程的代码。此外,它们可以根据中断请求单独执行操作。前面的示例包含任务和线程所有者。我们的代码是所有者,因为它创建了线程。处理中断是所有者的责任。例如,它可以终止线程或执行任何其他操作。由于我们不知道确切的影响,因此 只有所有者必须中断线程。
例如,在使用线程池时,任务不拥有工作线程,它们只是从基础池实现中借用它们。因此,当我们向线程池提交任务时,任务可以响应中断并可能取消其操作。但是它一定不能中断调用Thread.interrupt的工作线程。所述 的ExecutorService实现封装中断机制,并提供执行shutdownNow方法。这种方法使我们能够在线程池的监视下中断工作线程。
3.检查中断状态
在前面的示例中,我们使用Thread.isInterrupted 检查线程是否被中断。 当呼叫者执行此检查时,不会清除中断标志。换句话说,当多次调用时, isInterrupted返回相同的布尔值:
new Thread(() -> {
Thread.currentThread().interrupt();
System.out.println("Is thread interrupted: " + Thread.currentThread().isInterrupted());
System.out.println("Is thread interrupted: " + Thread.currentThread().isInterrupted());
}).start();
在这里,我们首先中断当前线程。然后连续的isInterrupted调用都返回true:
Is thread interrupted: true
Is thread interrupted: true
该线程类还提供了一个静态的变体: Thread.interrupted。它检查当前线程的中断状态。 另外,它清除中断标志:
new Thread(() -> {
Thread.currentThread().interrupt();
System.out.println("Is thread interrupted: " + Thread.interrupted());
System.out.println("Is thread interrupted: " + Thread.interrupted());
}).start();
输出显示此行为:
Is thread interrupted: true
Is thread interrupted: false
4.响应中断
接下来,我们将研究如何响应线程中断。如前所述,一个中断可以有多个接收者:任务和线程。
对于这些任务,我们必须使用它来取消正在运行的操作并在可能的情况下立即退出。这也是大多数Java类库采用的方法。
对于线程,如果代码不拥有线程(例如,在使用线程池时)或不负责应用中断策略,则必须保留中断请求。为此,我们必须抛出 InterruptedException或再次设置状态。
4.1。清除并抛出InterruptedException
作为第一种选择,我们将考虑在interrupt 上引发InterruptedException 。如果任务支持取消,则必须停止执行。取消之后,我们抛出一个 InterruptedException,以使堆栈上较高的代码处理该中断。
看一下示例:
public void handleAndThrowException() throws InterruptedException {
final ExecutorService executorService = Executors.newSingleThreadExecutor();
final Callable<Void> task = () -> {
while (true) {
if (Thread.interrupted()) {
System.out.println("Interrupted, cleaning up and then throwing exception.");
throw new InterruptedException();
}
// Do work.
}
};
final Future<?> future = executorService.submit(task);
TimeUnit.SECONDS.sleep(1); // Wait for some time
final boolean cancel = future.cancel(true);
System.out.println("Is cancelled?: " + cancel);
executorService.shutdown();
}
在这里,我们使用线程池来执行任务。在Callable任务中,我们有一个while循环执行一些工作。请注意,我们的任务依赖于线程中断来取消。当它检测到中断时,它将清除并引发 InterruptedException。这样,我们就会有礼貌地表现并保持中断状态,以便池有机会对其执行操作。注意,我们不是直接调用 Thread.interrupt 。相反,我们正在请求一个调用Future.cancel的中断。
4.2。再次清除并中断
在Runnable任务中,我们不能抛出InterruptedException。否则我们可能不想抛出一个检查异常。在这些情况下, 如果清除,我们必须再次设置中断状态。
while (true) {
if (Thread.interrupted()) {
System.out.println("Interrupted, cleaning up and then throwing exception.");
Thread.currentThread().interrupt();
return "Canceled";
}
// Do work.
}
与前面的示例相似,该任务对中断做出响应,并尽快停止。而且,它保持呼叫者代码的中断状态。
4.3。响应InterruptedException
如果我们的代码是调用者并捕获了InterruptedException,则我们具有与前面相同的选项。我们可以重新抛出异常,也可以设置调用中断方法的中断状态 :
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println("Interrupted, cleaning up and then setting the interrupt status.");
Thread.currentThread().interrupt();
return "Canceled";
}
return "Ok";
在这里,我们选择第二种方法并再次设置状态。
4.4。检查不清除
或者,我们可以使用 isInterrupted 检查状态。由于它不会清除状态,因此我们不需要抛出 InterruptedException或再次设置状态。
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Interrupted, cleaning up and then throwing exception.");
return "Canceled";
}
// Do work.
}
4.5吞下中断请求
最后,如果代码对线程应用了中断策略,则可以清除中断状态。这意味着代码不在线程池上运行,而是管理自己的线程。