怎样处理InterruptedExceptio

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

 

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

 

我们从这段代码开始:

 

while (true) {

  // Nothing

}

 

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

 

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

 

1     Thread loop = new Thread( new Runnable() {
2 
3         @Override
4         public void run() {
5           while (true) {
6                 //...
7             }
8         }
9     });

 

 

loop.start();

// Now how do we stop it?

 

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

 

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

 

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

 

 

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

 

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

 

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

 

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

 

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

 

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

 

 

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

 

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

 

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

 

try {

     Thread.sleep(100);

} catch (InterruptedException ex) {

     // Stop immediately and go home

}

 

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

 

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

 

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

 

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

 

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

 

 

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

 

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

 

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

 

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

 

    try {

        Thread.sleep(100);

    } catch (InterruptedException ex) {

         throw new RuntimeException(ex);

    }

 

 

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

 

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

 

这是我们应该做的:

 

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

 

 

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

 

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

 

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

posted @ 2015-11-25 10:48  kangye1014  阅读(239)  评论(0编辑  收藏  举报