如果没有将InterruptedException
检查为异常,则可能甚至没人会注意到它–实际上,这些年来可以防止出现几个错误。 但是由于必须对其进行处理,因此许多人不正确或不加考虑地处理它。 让我们以一个线程的简单示例为例,该线程定期进行一些清理,但大多数情况下在两次睡眠之间进行。
class Cleaner implements Runnable {Cleaner() {final Thread cleanerThread = new Thread(this, "Cleaner");cleanerThread.start();}@Overridepublic void run() {while(true) {cleanUp();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}private void cleanUp() {//...}}
此代码在很多层上都是错误的!
- 在某些环境中,在构造函数中启动
Thread
可能不是一个好主意,例如,某些框架(如Spring)将创建动态子类来支持方法拦截。 最后,我们将从两个实例运行两个线程。 - 吞下了
InterruptedException
,并且异常本身未正确记录 - 此类为每个实例启动一个新线程,它应改用
ScheduledThreadPoolExecutor
,在许多实例之间共享(更健壮和更有效地使用内存) - 同样,使用
ScheduledThreadPoolExecutor
我们可以避免自己编写睡眠/工作循环代码,并且还可以切换到固定速率,而不是此处介绍的固定延迟行为。 - 最后但并非最不重要的一点是,即使
Cleaner
实例不再被其他任何东西引用,也没有办法摆脱此线程。
所有问题都是有效的,但是吞没InterruptedException
是其最大的罪过。 在我们理解原因之前,让我们先思考一下该异常的含义以及如何利用它来优雅地中断线程。 JDK中的许多阻止操作都声明抛出InterruptedException
,包括:
-
Object.wait()
-
Thread.sleep()
-
Process.waitFor()
-
AsynchronousChannelGroup.awaitTermination()
-
java.util.concurrent.*
各种阻塞方法,例如ExecutorService.awaitTermination()
,Future.get()
,BlockingQueue.take()
,Semaphore.acquire()
Condition.await()
以及许多其他方法 -
SwingUtilities.invokeAndWait()
请注意,阻塞I / O不会引发InterruptedException
(这很可惜)。 如果所有这些类都声明了InterruptedException
,那么您可能想知道何时会抛出此异常?
- 当某个线程在声明
InterruptedException
某个方法上被阻塞并且您在该线程上调用Thread.interrupt()
时,很可能阻塞的方法将立即抛出InterruptedException
。 - 如果您将任务提交到线程池(
ExecutorService.submit()
),并且在执行任务时调用Future.cancel(true)
。 在这种情况下,线程池将尝试为您中断正在运行此类任务的线程,从而有效地中断了您的任务。
知道InterruptedException
实际含义后,我们就可以正确处理它。 如果有人试图中断我们的线程,而我们通过捕获InterruptedException
发现了它,则最合理的做法是让该线程完成,例如:
class Cleaner implements Runnable, AutoCloseable {private final Thread cleanerThread;Cleaner() {cleanerThread = new Thread(this, "Cleaner");cleanerThread.start();}@Overridepublic void run() {try {while (true) {cleanUp();TimeUnit.SECONDS.sleep(1);}} catch (InterruptedException ignored) {log.debug("Interrupted, closing");}}//... @Overridepublic void close() {cleanerThread.interrupt();}
}
注意, try-catch
块现在围绕while
循环。 这样,如果sleep()
抛出InterruptedException
,我们将跳出循环。 您可能会争辩说,我们应该记录InterruptedException
的堆栈跟踪。 这取决于情况,因为在这种情况下中断线程是我们真正希望的,而不是失败。 但这取决于你。 最重要的是,如果sleep()
被另一个线程中断,我们将很快完全脱离run()
。 如果您非常小心,您可能会问,如果线程在cleanUp()
方法中而不是在睡眠时中断线程,会发生什么情况? 通常,您会遇到这样的手动标记:
private volatile boolean stop = false;@Override
public void run() {while (!stop) {cleanUp();TimeUnit.SECONDS.sleep(1);}
}@Override
public void close() {stop = true;
}
但是请注意, stop
标志(必须是volatile
!)不会中断阻塞操作,我们必须等到sleep()
完成。 另一方面,有人可能会争辩说,显式flag
可以更好地控制我们,因为我们可以随时监视其值。 事实证明,线程中断的工作方式相同。 如果有人在执行非阻塞计算时中断了线程(例如在cleanUp()
内部),则此类计算不会立即中断。 但是,线程被标记为已中断,并且随后的所有阻塞操作(例如sleep()
)都将立即立即抛出InterruptedException
因此我们不会丢失该信号。
如果我们编写仍想利用线程中断功能的非阻塞线程,那么我们也可以利用这一事实。 不必依赖于InterruptedException
我们只需定期检查Thread.isInterrupted()
:
public void run() {while (Thread.currentThread().isInterrupted()) {someHeavyComputations();}
}
在上方,如果有人中断了我们的线程,则在someHeavyComputations()
返回时我们将立即放弃计算。 如果它运行了两个长时间或无限期,我们将永远不会发现中断标志。 有趣的是, interrupted
标志不是一次性的 。 我们可以调用Thread.interrupted()
而不是isInterrupted()
,这将重置interrupted
标志并且我们可以继续。 有时您可能想忽略中断标志并继续运行。 在这种情况下, interrupted()
可能会派上用场。 顺便说一句,我(不精确地)称“吸气剂”来改变被观察物体的状态 “ 海森格 ”。
注意
如果您是老派程序员,则可能会想起Thread.stop()
方法,该方法已被弃用10年了 。 在Java 8中,有计划“取消实现” ,但是在1.8u5中,它仍然存在。 但是,不要使用它,而是使用Thread.stop()
将任何代码重构为Thread.interrupt()
。
番石榴的
很少您可能会完全忽略InterruptedException
。 在这种情况下,请查看Guava的Uninterruptibles
。 它具有许多实用方法,例如sleepUninterruptibly()
或awaitUninterruptibly(CountDownLatch)
。 只是要小心他们。 我知道他们没有声明InterruptedException
(可能很少),但是它们也完全防止了当前线程被中断–这是非常不寻常的。
摘要
到现在为止,我希望您对为什么某些方法引发InterruptedException
有所了解。 主要的收获是:
- 捕获的
InterruptedException
应该得到正确处理-大多数情况下,这意味着完全脱离当前任务/循环/线程 - 吞下
InterruptedException
很少是一个好主意 - 如果线程不在阻塞调用中时被中断,请使用
isInterrupted()
。 当线程已经被中断时也进入阻塞方法应该立即抛出InterruptedException
。
翻译自: https://www.javacodegeeks.com/2014/06/interruptedexception-and-interrupting-threads-explained.html