中止Java线程,当心interrupt()方法

来源:http://blog.csdn.net/wxwzy738/article/details/8516253java

程序是很简易的。然而,在编程人员面前,多线程呈现出了一组新的难题,若是没有被恰当的解决,将致使意外的行为以及细微的、难以发现的错误。
  在本篇文章中,咱们针对这些难题之一:如何中断一个正在运行的线程。 
编程

背景 
    中断(Interrupt)一个线程意味着在该线程完成任务以前中止其正在进行的一切,有效地停止其当前的操做。线程是死亡、仍是等待新的任务或是继续运行至下一步,就取决于这个程序。虽然初次看来它可能显得简单,可是,你必须进行一些预警以实现指望的结果。你最好仍是牢记如下的几点告诫。 

    首先,忘掉Thread.stop方法。虽然它确实中止了一个正在运行的线程,然而,这种方法是不安全也是不受提倡的,这意味着,在将来的JAVA版本中,它将不复存在。 

    一些轻率的家伙可能被另外一种方法Thread.interrupt所迷惑。尽管,其名称彷佛在暗示着什么,然而,这种方法并不会中断一个正在运行的线程(待会将进一步说明),正如Listing A中描述的那样。它建立了一个线程,而且试图使用Thread.interrupt方法中止该线程。Thread.sleep()方法的调用,为线程的初始化和停止提供了充裕的时间。线程自己并不参与任何有用的操做。 
Listing A 
安全

 

[java]  view plain copy
 
  1. class Example1 extends Thread {  
  2.             boolean stop=false;  
  3.             public static void main( String args[] ) throws Exception {  
  4.             Example1 thread = new Example1();  
  5.             System.out.println( "Starting thread..." );  
  6.             thread.start();  
  7.             Thread.sleep( 3000 );  
  8.             System.out.println( "Interrupting thread..." );  
  9.             thread.interrupt();  
  10.             Thread.sleep( 3000 );  
  11.             System.out.println("Stopping application..." );  
  12.             //System.exit(0);  
  13.             }  
  14.             public void run() {  
  15.             while(!stop){  
  16.             System.out.println( "Thread is running..." );  
  17.             long time = System.currentTimeMillis();  
  18.             while((System.currentTimeMillis()-time < 1000)) {  
  19.             }  
  20.             }  
  21.             System.out.println("Thread exiting under request..." );  
  22.             }  
  23.             }  

若是你运行了Listing A中的代码,你将在控制台看到如下输出: 

Starting thread... 

Thread is running... 

Thread is running... 

Thread is running... 

Interrupting thread... 

Thread is running... 

Thread is running... 

Thread is running... 

Stopping application... 

Thread is running... 

Thread is running... 

Thread is running... 
............................... 
甚至,在Thread.interrupt()被调用后,线程仍然继续运行。 

真正地中断一个线程 

    中断线程最好的,最受推荐的方式是,使用共享变量(shared variable)发出信号,告诉线程必须中止正在运行的任务。线程必须周期性的核查这一变量(尤为在冗余操做期间),而后有秩序地停止任务。Listing B描述了这一方式。 

Listing B 服务器

[java]  view plain copy
 
  1. class Example2 extends Thread {  
  2.   volatile boolean stop = false;  
  3.   public static void main( String args[] ) throws Exception {  
  4.    Example2 thread = new Example2();  
  5.    System.out.println( "Starting thread..." );  
  6.    thread.start();  
  7.    Thread.sleep( 3000 );  
  8.    System.out.println( "Asking thread to stop..." );  
  9.   
  10.    thread.stop = true;  
  11.    Thread.sleep( 3000 );  
  12.    System.out.println( "Stopping application..." );  
  13.    //System.exit( 0 );  
  14.   }  
  15.   
  16.   public void run() {  
  17.     while ( !stop ) {  
  18.      System.out.println( "Thread is running..." );  
  19.       long time = System.currentTimeMillis();  
  20.       while ( (System.currentTimeMillis()-time < 1000) && (!stop) ) {  
  21.       }  
  22.     }  
  23.    System.out.println( "Thread exiting under request..." );  
  24.   }  
  25. }  


运行Listing B中的代码将产生以下输出(注意线程是如何有秩序的退出的) 

Starting thread... 

Thread is running... 

Thread is running... 

Thread is running... 

Asking thread to stop... 

Thread exiting under request... 

Stopping application... 

   虽然该方法要求一些编码,但并不难实现。同时,它给予线程机会进行必要的清理工做,这在任何一个多线程应用程序中都是绝对须要的。请确认将共享变量定义成volatile 类型或将对它的一切访问封入同步的块/方法(synchronized blocks/methods)中。 

到目前为止一切顺利!可是,当线程等待某些事件发生而被阻塞,又会发生什么?固然,若是线程被阻塞,它便不能核查共享变量,也就不能中止。这在许多状况下会发生,例如调用Object.wait()、ServerSocket.accept()和DatagramSocket.receive()时,这里仅举出一些。 

他们均可能永久的阻塞线程。即便发生超时,在超时期满以前持续等待也是不可行和不适当的,因此,要使用某种机制使得线程更早地退出被阻塞的状态。 

很不幸运,不存在这样一种机制对全部的状况都适用,可是,根据状况不一样却可使用特定的技术。在下面的环节,我将解答一下最广泛的例子。 

使用Thread.interrupt()中断线程 

  正如Listing A中所描述的,Thread.interrupt()方法不会中断一个正在运行的线程。这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,若是线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提前地终结被阻塞状态。 

    所以,若是线程被上述几种方法阻塞,正确的中止线程方式是设置共享变量,并调用interrupt()(注意变量应该先设置)。若是线程没有被阻塞,这时调用interrupt()将不起做用;不然,线程就将获得异常(该线程必须事先预备好处理此情况),接着逃离阻塞状态。在任何一种状况中,最后线程都将检查共享变量而后再中止。Listing C这个示例描述了该技术。 

Listing C 网络

 

 

[java]  view plain copy
 
  1. class Example3 extends Thread {  
  2.   volatile boolean stop = false;  
  3.   public static void main( String args[] ) throws Exception {  
  4.    Example3 thread = new Example3();  
  5.    System.out.println( "Starting thread..." );  
  6.    thread.start();  
  7.    Thread.sleep( 3000 );  
  8.    System.out.println( "Asking thread to stop..." );  
  9.    thread.stop = true;//若是线程阻塞,将不会检查此变量  
  10.    thread.interrupt();  
  11.    Thread.sleep( 3000 );  
  12.    System.out.println( "Stopping application..." );  
  13.    //System.exit( 0 );  
  14.   }  
  15.   
  16.   public void run() {  
  17.     while ( !stop ) {  
  18.      System.out.println( "Thread running..." );  
  19.       try {  
  20.       Thread.sleep( 1000 );  
  21.       } catch ( InterruptedException e ) {  
  22.       System.out.println( "Thread interrupted..." );  
  23.       }  
  24.     }  
  25.    System.out.println( "Thread exiting under request..." );  
  26.   }  
  27. }  


一旦Listing C中的Thread.interrupt()被调用,线程便收到一个异常,因而逃离了阻塞状态并肯定应该中止。运行以上代码将获得下面的输出: 

Starting thread... 

Thread running... 

Thread running... 

Thread running... 

Asking thread to stop... 

Thread interrupted... 

Thread exiting under request... 

Stopping application... 


中断I/O操做 
    然而,若是线程在I/O操做进行时被阻塞,又会如何?I/O操做能够阻塞线程一段至关长的时间,特别是牵扯到网络应用时。例如,服务器可能须要等待一个请求(request),又或者,一个网络应用程序可能要等待远端主机的响应。 

若是你正使用通道(channels)(这是在Java 1.4中引入的新的I/O API),那么被阻塞的线程将收到一个ClosedByInterruptException异常。若是状况是这样,其代码的逻辑和第三个例子中的是同样的,只是异常不一样而已。 

可是,你可能正使用Java1.0以前就存在的传统的I/O,并且要求更多的工做。既然这样,Thread.interrupt()将不起做用,由于线程将不会退出被阻塞状态。Listing D描述了这一行为。尽管interrupt()被调用,线程也不会退出被阻塞状态 

Listing D 多线程

 

 

[java]  view plain copy
 
  1. import java.io.*;  
  2. class Example4 extends Thread {  
  3.   public static void main( String args[] ) throws Exception {  
  4.     Example4 thread = new Example4();  
  5.    System.out.println( "Starting thread..." );  
  6.    thread.start();  
  7.    Thread.sleep( 3000 );  
  8.    System.out.println( "Interrupting thread..." );  
  9.    thread.interrupt();  
  10.    Thread.sleep( 3000 );  
  11.    System.out.println( "Stopping application..." );  
  12.    //System.exit( 0 );  
  13.   }  
  14.   
  15.   public void run() {  
  16.    ServerSocket socket;  
  17.     try {  
  18.       socket = new ServerSocket(7856);  
  19.     } catch ( IOException e ) {  
  20.      System.out.println( "Could not create the socket..." );  
  21.       return;  
  22.     }  
  23.     while ( true ) {  
  24.      System.out.println( "Waiting for connection..." );  
  25.       try {  
  26.        Socket sock = socket.accept();  
  27.       } catch ( IOException e ) {  
  28.       System.out.println( "accept() failed or interrupted..." );  
  29.       }  
  30.     }  
  31.   }  
  32. }  


 很幸运,Java平台为这种情形提供了一项解决方案,即调用阻塞该线程的套接字的close()方法。在这种情形下,若是线程被I/O操做阻塞,该线程将接收到一个SocketException异常,这与使用interrupt()方法引发一个InterruptedException异常被抛出很是类似。 

惟一要说明的是,必须存在socket的引用(reference),只有这样close()方法才能被调用。这意味着socket对象必须被共享。Listing E描述了这一情形。运行逻辑和之前的示例是相同的。 

Listing E app

 

 

[java]  view plain copy
 
  1. import java.net.*;  
  2. import java.io.*;  
  3. class Example5 extends Thread {  
  4.   volatile boolean stop = false;  
  5.   volatile ServerSocket socket;  
  6.   public static void main( String args[] ) throws Exception {  
  7.     Example5 thread = new Example5();  
  8.    System.out.println( "Starting thread..." );  
  9.    thread.start();  
  10.    Thread.sleep( 3000 );  
  11.    System.out.println( "Asking thread to stop..." );  
  12.    thread.stop = true;  
  13.    thread.socket.close();  
  14.    Thread.sleep( 3000 );  
  15.    System.out.println( "Stopping application..." );  
  16.    //System.exit( 0 );  
  17.   }  
  18.   public void run() {  
  19.     try {  
  20.       socket = new ServerSocket(7856);  
  21.     } catch ( IOException e ) {  
  22.      System.out.println( "Could not create the socket..." );  
  23.       return;  
  24.     }  
  25.     while ( !stop ) {  
  26.      System.out.println( "Waiting for connection..." );  
  27.       try {  
  28.        Socket sock = socket.accept();  
  29.       } catch ( IOException e ) {  
  30.       System.out.println( "accept() failed or interrupted..." );  
  31.       }  
  32.     }  
  33.    System.out.println( "Thread exiting under request..." );  
  34.   }  
  35. }  


如下是运行Listing E中代码后的输出: 

Starting thread... 

Waiting for connection... 

Asking thread to stop... 

accept() failed or interrupted... 

Thread exiting under request... 

Stopping application... 

多线程是一个强大的工具,然而它正呈现出一系列难题。其中之一是如何中断一个正在运行的线程。若是恰当地实现,使用上述技术中断线程将比使用Java平台上已经提供的内嵌操做更为简单。socket

相关文章
相关标签/搜索