在JAVA中
通過其對線程類的內嵌支持
編程人員編寫多線程程序是很簡易的
然而
在編程人員面前
多線程呈現出一組新的難題
如果沒有被恰當的解決
將導致意外的行為以及細微的
難以發現的錯誤
在本篇文章中
我們針對這些難題之一
如何中斷一個正在運行的線程
背景
中斷(Interrupt)一個線程意味著在該線程完成任務之前停止其正在進行的一切有效地中止其當前的操作線程是死亡還是等待新的任務或是繼續運行至下一步就取決於這個程序
雖然初次看來它可能顯得簡單但是你必須進行一些預警以實現期望的結果你最好還是牢記以下的幾點告誡
首先忘掉Threadstop方法雖然它確實停止了一個正在運行的線程然而這種方法是不安全也是不受提倡的這意味著在未來的JAVA版本中它將不復存在
一些輕率的家伙可能被另一種方法Threadinterrupt所迷惑盡管其名稱似乎在暗示著什麼然而這種方法並不會中斷一個正在運行的線程(待會將進一步說明)正如Listing A中描述的那樣它創建了一個線程並且試圖使用Threadinterrupt方法停止該線程Threadsleep()方法的調用為線程的初始化和中止提供了充裕的時間線程本身並不參與任何有用的操作
Listing A
class Example extends Thread {
public static void main( String args[] ) throws Exception {
Example thread = new Example();
Systemoutprintln( Starting thread );
threadstart();
Threadsleep( );
Systemoutprintln( Interrupting thread );
threadinterrupt();
Threadsleep( );
Systemoutprintln( Stopping application );
Systemexit( );
}
}
如果你運行了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
甚至在Threadinterrupt()被調用後線程仍然繼續運行了一段時間
真正地中斷一個線程
中斷線程最好的最受推薦的方式是使用共享變量(shared variable)發出信號告訴線程必須停止正在運行的任務線程必須周期性的核查這一變量(尤其在冗余操作期間)然後有秩序地中止任務Listing B描述了這一方式
Listing B
class Example extends Thread {
volatile boolean stop = false;
public static void main( String args[] ) throws Exception {
Example thread = new Example();
Systemoutprintln( Starting thread );
threadstart();
Threadsleep( );
Systemoutprintln( Asking thread to stop );
threadstop = true;
Threadsleep( );
Systemoutprintln( Stopping application );
Systemexit( );
}
public void run() {
while ( !stop ) {
Systemoutprintln( Thread is running );
long time = SystemcurrentTimeMillis();
while ( (SystemcurrentTimeMillis()time < ) && (!stop) ) {
}
}
Systemoutprintln( 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)中
到目前為止一切順利!但是當線程等待某些事件發生而被阻塞又會發生什麼?當然如果線程被阻塞它便不能核查共享變量也就不能停止這在許多情況下會發生例如調用Objectwait()ServerSocketaccept()和DatagramSocketreceive()時這裡僅舉出一些
他們都可能永久的阻塞線程即使發生超時在超時期滿之前持續等待也是不可行和不適當的所以要使用某種機制使得線程更早地退出被阻塞的狀態
很不幸運不存在這樣一種機制對所有的情況都適用但是根據情況不同卻可以使用特定的技術在下面的環節我將解答一下最普遍的例子
使用Threadinterrupt()中斷線程
正如Listing A中所描述的Threadinterrupt()方法不會中斷一個正在運行的線程這一方法實際上完成的是在線程受到阻塞時拋出一個中斷信號這樣線程就得以退出阻塞的狀態更確切的說如果線程被Objectwait Threadjoin和Threadsleep三種方法之一阻塞那麼它將接收到一個中斷異常(InterruptedException)從而提早地終結被阻塞狀態
因此如果線程被上述幾種方法阻塞正確的停止線程方式是設置共享變量並調用interrupt()(注意變量應該先設置)如果線程沒有被阻塞這時調用interrupt()將不起作用否則線程就將得到異常(該線程必須事先預備好處理此狀況)接著逃離阻塞狀態在任何一種情況中最後線程都將檢查共享變量然後再停止Listing C這個示例描述了該技術
Listing C
class Example extends Thread {
volatile boolean stop = false;
public static void main( String args[] ) throws Exception {
Example thread = new Example();
Systemoutprintln( Starting thread );
threadstart();
Threadsleep( );
Systemoutprintln( Asking thread to stop );
threadstop = true;
threadinterrupt();
Threadsleep( );
Systemoutprintln( Stopping application );
Systemexit( );
}
public void run() {
while ( !stop ) {
Systemoutprintln( Thread running );
try {
Threadsleep( );
} catch ( InterruptedException e ) {
Systemoutprintln( Thread interrupted );
}
}
Systemoutprintln( Thread exiting under request );
}
}
一旦Listing C中的Threadinterrupt()被調用線程便收到一個異常於是逃離了阻塞狀態並確定應該停止運行以上代碼將得到下面的輸出
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 中引入的新的I/O API)那麼被阻塞的線程將收到一個ClosedByInterruptException異常如果情況是這樣其代碼的邏輯和第三個例子中的是一樣的只是異常不同而已
但是你可能正使用Java之前就存在的傳統的I/O因為新的I/O是最近才引入而且要求更多的工作既然這樣Threadinterrupt()將不起作用因為線程將不會退出被阻塞狀態Listing D描述了這一行為盡管interrupt()被調用線程也不會退出被阻塞狀態
Listing D
import javaio*;
class Example extends Thread {
public static void main( String args[] ) throws Exception {
Example thread = new Example();
Systemoutprintln( Starting thread );
threadstart();
Threadsleep( );
Systemoutprintln( Interrupting thread );
threadinterrupt();
Threadsleep( );
Systemoutprintln( Stopping application );
Systemexit( );
}
public void run() {
ServerSocket socket;
try {
socket = new ServerSocket();
} catch ( IOException e ) {
Systemoutprintln( Could not create the socket );
return;
}
while ( true ) {
Systemoutprintln( Waiting for connection );
try {
Socket sock = socketaccept();
} catch ( IOException e ) {
Systemoutprintln( accept() failed or interrupted );
}
}
}
}
很幸運Java平台為這種情形提供了一項解決方案即調用阻塞該線程的套接字的close()方法在這種情形下如果線程被I/O操作阻塞該線程將接收到一個SocketException異常這與使用interrupt()方法引起一個InterruptedException異常被拋出非常相似
唯一要說明的是必須存在socket的引用(reference)只有這樣close()方法才能被調用這意味著socket對象必須被共享Listing E描述了這一情形運行邏輯和以前的示例是相同的
Listing E
import javanet*;
import javaio*;
class Example extends Thread {
volatile boolean stop = false;
volatile ServerSocket socket;
public static void main( String args[] ) throws Exception {
Example thread = new Example();
Systemoutprintln( Starting thread );
threadstart();
Threadsleep( );
Systemoutprintln( Asking thread to stop );
threadstop = true;
threadsocketclose();
Threadsleep( );
Systemoutprintln( Stopping application );
Systemexit( );
}
public void run() {
try {
socket = new ServerSocket();
} catch ( IOException e ) {
Systemoutprintln( Could not create the socket );
return;
}
while ( !stop ) {
Systemoutprintln( Waiting for connection );
try {
Socket sock = socketaccept();
} catch ( IOException e ) {
Systemoutprintln( accept() failed or interrupted );
}
}
Systemoutprintln( 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平台上已經提供的內嵌操作更為簡單
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27663.html