中止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

相關文章
相關標籤/搜索