Posted on 2006-04-12 13:36
fortune 閱讀(587)
評論(0) 編輯 收藏 所屬分類:
java技術
在JAVA中,通過其對線程類的內嵌支持,編程人員編寫多線程程序是很簡易的。然而,在編程人員面前,多線程呈現出了一組新的難題,如果沒有被恰當的解決,將導致意外的行為以及細微的、難以發現的錯誤。在本篇文章中,我們針對這些難題之一:如何中斷一個正在運行的線程。?
背景
中斷(Interrupt)一個線程意味著在該線程完成任務之前停止其正在進行的一切,有效地中止其當前的操作。線程是死亡、還是等待新的任務或是繼續運行至下一步,就取決于這個程序。
雖然初次看來它可能顯得簡單,但是,你必須進行一些預警以實現期望的結果。你最好還是牢記以下的幾點告誡。
首先,忘掉Thread.stop方法。雖然它確實停止了一個正在運行的線程,然而,這種方法是不安全也是不受提倡的,這意味著,在未來的JAVA版本中,它將不復存在。
一些輕率的家伙可能被另一種方法Thread.interrupt所迷惑。盡管,其名稱似乎在暗示著什么,然而,這種方法并不會中斷一個正在運行的線程(待會將進一步說明),正如Listing?A中描述的那樣。它創建了一個線程,并且試圖使用Thread.interrupt方法停止該線程。Thread.sleep()方法的調用,為線程的初始化和中止提供了充裕的時間。線程本身并不參與任何有用的操作。
Listing?A
class?Example1?extends?Thread?{
??public?static?void?main(?String?args[]?)?throws?Exception?{
????Example1?thread?=?new?Example1();
???System.out.println(?"Starting?thread..."?);
???thread.start();
???Thread.sleep(?3000?);
???System.out.println(?"Interrupting?thread..."?);
???thread.interrupt();
???Thread.sleep(?3000?);
???System.out.println(?"Stopping?application..."?);
???System.exit(?0?);
??}
如果你運行了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.interrupt()被調用后,線程仍然繼續運行了一段時間。
真正地中斷一個線程
中斷線程最好的,最受推薦的方式是,使用共享變量(shared?variable)發出信號,告訴線程必須停止正在運行的任務。線程必須周期性的核查這一變量(尤其在冗余操作期間),然后有秩序地中止任務。Listing?B描述了這一方式。
Listing?B
class?Example2?extends?Thread?{
??volatile?boolean?stop?=?false;
??public?static?void?main(?String?args[]?)?throws?Exception?{
????Example2?thread?=?new?Example2();
???System.out.println(?"Starting?thread..."?);
???thread.start();
???Thread.sleep(?3000?);
???System.out.println(?"Asking?thread?to?stop..."?);
???thread.stop?=?true;
???Thread.sleep(?3000?);
???System.out.println(?"Stopping?application..."?);
???System.exit(?0?);
??}
??public?void?run()?{
????while?(?!stop?)?{
?????System.out.println(?"Thread?is?running..."?);
??????long?time?=?System.currentTimeMillis();
??????while?(?(System.currentTimeMillis()-time?<?1000)?&&?(!stop)?)?{
??????}
????}
???System.out.println(?"Thread?exiting?under?request..."?);
??}
}
?
運行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
class?Example3?extends?Thread?{
??volatile?boolean?stop?=?false;
??public?static?void?main(?String?args[]?)?throws?Exception?{
???Example3?thread?=?new?Example3();
???System.out.println(?"Starting?thread..."?);
???thread.start();
???Thread.sleep(?3000?);
???System.out.println(?"Asking?thread?to?stop..."?);
???thread.stop?=?true;
???thread.interrupt();
???Thread.sleep(?3000?);
???System.out.println(?"Stopping?application..."?);
???System.exit(?0?);
??}
??public?void?run()?{
????while?(?!stop?)?{
?????System.out.println(?"Thread?running..."?);
??????try?{
??????Thread.sleep(?1000?);
??????}?catch?(?InterruptedException?e?)?{
??????System.out.println(?"Thread?interrupted..."?);
??????}
????}
???System.out.println(?"Thread?exiting?under?request..."?);
??}
}
一旦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,因為新的I/O是最近才引入,而且要求更多的工作。既然這樣,Thread.interrupt()將不起作用,因為線程將不會退出被阻塞狀態。Listing?D描述了這一行為。盡管interrupt()被調用,線程也不會退出被阻塞狀態
Listing?D
import?java.io.*;
class?Example4?extends?Thread?{
??public?static?void?main(?String?args[]?)?throws?Exception?{
????Example4?thread?=?new?Example4();
???System.out.println(?"Starting?thread..."?);
???thread.start();
???Thread.sleep(?3000?);
???System.out.println(?"Interrupting?thread..."?);
???thread.interrupt();
???Thread.sleep(?3000?);
???System.out.println(?"Stopping?application..."?);
???System.exit(?0?);
??}
??public?void?run()?{
???ServerSocket?socket;
????try?{
??????socket?=?new?ServerSocket(7856);
????}?catch?(?IOException?e?)?{
?????System.out.println(?"Could?not?create?the?socket..."?);
??????return;
????}
????while?(?true?)?{
?????System.out.println(?"Waiting?for?connection..."?);
??????try?{
???????Socket?sock?=?socket.accept();
??????}?catch?(?IOException?e?)?{
??????System.out.println(?"accept()?failed?or?interrupted..."?);
??????}
????}
??}
}
很幸運,Java平臺為這種情形提供了一項解決方案,即調用阻塞該線程的套接字的close()方法。在這種情形下,如果線程被I/O操作阻塞,該線程將接收到一個SocketException異常,這與使用interrupt()方法引起一個InterruptedException異常被拋出非常相似。
唯一要說明的是,必須存在socket的引用(reference),只有這樣close()方法才能被調用。這意味著socket對象必須被共享。Listing?E描述了這一情形。運行邏輯和以前的示例是相同的。
Listing?E
import?java.net.*;
import?java.io.*;
class?Example5?extends?Thread?{
??volatile?boolean?stop?=?false;
??volatile?ServerSocket?socket;
??public?static?void?main(?String?args[]?)?throws?Exception?{
????Example5?thread?=?new?Example5();
???System.out.println(?"Starting?thread..."?);
???thread.start();
???Thread.sleep(?3000?);
???System.out.println(?"Asking?thread?to?stop..."?);
???thread.stop?=?true;
???thread.socket.close();
???Thread.sleep(?3000?);
???System.out.println(?"Stopping?application..."?);
???System.exit(?0?);
??}
??public?void?run()?{
????try?{
??????socket?=?new?ServerSocket(7856);
????}?catch?(?IOException?e?)?{
?????System.out.println(?"Could?not?create?the?socket..."?);
??????return;
????}
????while?(?!stop?)?{
?????System.out.println(?"Waiting?for?connection..."?);
??????try?{
???????Socket?sock?=?socket.accept();
??????}?catch?(?IOException?e?)?{
??????System.out.println(?"accept()?failed?or?interrupted..."?);
??????}
????}
???System.out.println(?"Thread?exiting?under?request..."?);
??}
}
以下是運行Listing?E中代碼后的輸出:
Starting?thread...
Waiting?for?connection...
Asking?thread?to?stop...
accept()?failed?or?interrupted...
Thread?exiting?under?request...
Stopping?application...
多線程是一個強大的工具,然而它正呈現出一系列難題。其中之一是如何中斷一個正在運行的線程。如果恰當地實現,使用上述技術中斷線程將比使用Java平臺上已經提供的內嵌操作更為簡單。