你的位置:首页 > Java教程

[Java教程]怎样处理InterruptedException


Java 中的受检查异常 InterruptedException 如何处理是令人头痛的问题,下面是我对处理这个问题的理解。

Java 中的 InterruptedException 一直是一个令人头疼的问题,对初级开发者来说尤其如此。但实际上不应如此,这其实是一个很容易理解的问题。我会尽可能简单地描述这个问题。

我们从这段代码开始:

1
2
3
while (true) {
  // Nothing
}



它做了什么?什么都没做,只是无止境的消耗 CPU。我们能终止它吗?在 Java 中是不行的。只有当你按下 Ctrl-C 来终止整个 JVM 时这段程序才会停止。在 Java 中没有方式来终止一个线程,除非该线程自动退出。请务必牢记的这一原则,其它东西就显而易见了。

我们将这个死循环放在一个线程里:

1
2
3
4
5
6
7
8
9
10
11
Thread loop = new Thread(
  new Runnable() {
    @Override
    public void run() {
      while (true) {
      }
    }
  }
);
loop.start();
// Now how do we stop it?



所以,怎样才能停止一个需要停止的线程?

下面是 Java 中设计终止一个线程的方法。在线程的外部,设置一个标识变量(flag),然后在线程内部检查改标识变量,从而实现线程的终止。过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Thread loop = new Thread(
  new Runnable() {
    @Override
    public void run() {
      while (true) {
        if (Thread.interrupted()) {
          break;
        }
        // Continue to do nothing
      }
    }
  }
);
loop.start();
loop.interrupt();



这是终止线程的唯一方式,在这个例子里使用了两个方法。当调用 loop.interrupt() ,线程内部将标志位设置为 true。当调用 interrupted() 时,立即返回,并将标识变量设置为 false。确实,这个方法就是这样设计的。检查标识变量、返回、设置为 false。我知道这很丑陋。

因此,我从来没有在线程内调用 Thread.interrupted() 方法,因此标识变量为 true 时线程不会退出,没有人能停止这个线程。准确地说,我会忽略他们对 interrupt() 方法的调用。虽然它们会要求终止线程,但是我会忽略它们。它们不能让线程中断。

因此,总结一下我们现在理解的内容,一种合理的设计是通过检查标识变量来优雅地终止线程。如果代码中不检测标识变量,也不调用 Thread.interrupted(),那么终止线程的方式就只能按下 Ctrl-C 了。

现在你听明白这个逻辑了吗?我希望是。

现在,JDK 中有一些方法来检测标识变量,如果设置该标识变量,则会抛出 InterruptedException。例如,Thread.sleep() 方法的设计(一种最基本的方法):

1
2
3
4
5
6
7
8
9
public static void sleep(long millis)
  throws InterruptedException {
  while (/* You still need to wait */) {
    if (Thread.interrupted()) {
      throw new InterruptedException();
    }
    // Keep waiting
  }
}



为什么要这么做?为什么不能等待并且不用去检查标识变量?我相信一定有一个非常好的理由。理由如下(如果我说错了,请修正我的错误):为了让代码变快或是中断准备,没有其他理由。

如果你的代码足够快,你从来不会检测中断标识变量,因为你不想处理任何中断。如果你代码很慢,可能需要执行数秒,这时你就有可能需要处理中断了。

这就是为什么 InterruptedException 是受检查异常。这种设计告诉你,如果你想在几毫秒内停止线程,确定你已经做好中断准备。实践中一般做如下处理:

1
2
3
4
5
try {
  Thread.sleep(100);
} catch (InterruptedException ex) {
  // Stop immediately and go home
}



现在,你可以将它抛给负责捕获该异常的上级程序去处理。这种观点是有人在使用线程,并且会捕获该异常。理想情况下,会终止线程,因为这就是标识变量的功能。如果抛出 InterruptedException,就意味着有人在检查标识变量,线程需要尽可能快地终止。

线程的拥有者不想再等待线程执行,我们应该尊重拥有者的决定。

因此,当捕获到 InterruptedException 时,你应该完成相关的操作再退出线程。

现在,我们再看一下 Thread.sleep() 的代码:

1
2
3
4
5
6
7
8
public static void sleep(long millis)
  throws InterruptedException {
  while (/* ... */) {
    if (Thread.interrupted()) {
      throw new InterruptedException();
    }
  }
}



请记住,Thread.interrupted() 不仅仅是返回标识变量的值,而且会将标识变量的值设置为 false。因此,一旦抛出 InterruptedException 异常,标志变量将会重置。线程不再收到任何拥有者发送的中断请求。

线程的所有者要求停止线程,Thread.sleep() 监测到该请求并将其删除,再抛出 InterruptedException。如果你再次调用 Thread.sleep(),就不会响应任何中断请求,也不会抛出任何异常。

知道我想要说的是什么吗?不要丢失 InterruptedException,这一点非常重要。我们不能吞噬该异常并继续运行。这严重违背了 Java 多线程原则。所有者(线程的所有者)要求停止线程,而我们却将其忽略,这是非常不好的想法。

下面是大多数人对 InterruptedException 的处理:

1
2
3
4
5
try {
  Thread.sleep(100);
} catch (InterruptedException ex) {
  throw new RuntimeException(ex);
}



这看起来是符合逻辑的,但是这不能保证上层程序真正停止并退出。上层可能捕获了运行时异常,所以这个线程还是存活的。线程所有者将会非常失望。

我们必须通知上层捕获了一个中断请求。我们不能只抛出运行时异常,这种行为太不负责了。当一个线程接收一个中断请求时,我们不能只是将其转换成为一个 RuntimeException。我们不能将这种严峻的情况如此轻松地对待。

这是我们应该做的:

1
2
3
4
5
6
try {
  Thread.sleep(100);
} catch (InterruptedException ex) {
  Thread.currentThread().interrupt(); // Here!
  throw new RuntimeException(ex);
}



我们需要将标识变量重新设置为 true。

现在,没有人会谴责我们以不负责的态度来处理标识变量。我们发现其状态为 true,将其清理,重新设置为 true,最后抛出运行时异常。接下来会发生什么?我们已经不关心了。

这就是我认为的处理方式。你可以找到这个问题更详细的官方描述:Java 理论与实践:InterruptedException 处理

原文链接: https://dzone.com/articles/how-to-handle-the-interruptedexception 翻译:ImportNew.com - paddx
译文链接: http://www.importnew.com/17027.html

 

全能程序员交流QQ群290551701,聚集很多互联网精英,技术总监,架构师,项目经理!开源技术研究,欢迎业内人士,大牛及新手有志于从事IT行业人员进入!