中斷Java線程
發(fā)表時間:2024-02-09 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]由于可能導(dǎo)致異常行為的產(chǎn)生,多線程技術(shù)顯然對于開發(fā)人員來說提出了一系列新的挑戰(zhàn)。本文,我們將就這些挑戰(zhàn)之一:如何中斷一個正在運(yùn)行的線程展開討論。 在Java中通過其內(nèi)建的線程支持,編寫多線程的程序還是相當(dāng)簡單的。然而,采用多線程技術(shù)將對程序開發(fā)人員提出了一些列的挑戰(zhàn),如果沒有得到正確的處理,可能...
由于可能導(dǎo)致異常行為的產(chǎn)生,多線程技術(shù)顯然對于開發(fā)人員來說提出了一系列新的挑戰(zhàn)。本文,我們將就這些挑戰(zhàn)之一:如何中斷一個正在運(yùn)行的線程展開討論。
在Java中通過其內(nèi)建的線程支持,編寫多線程的程序還是相當(dāng)簡單的。然而,采用多線程技術(shù)將對程序開發(fā)人員提出了一些列的挑戰(zhàn),如果沒有得到正確的處理,可能會導(dǎo)致異常行為的產(chǎn)生,以及難以發(fā)現(xiàn)的差錯。本文,我們將就這些挑戰(zhàn)之一:如何中斷一個正在運(yùn)行的線程展開討論。
背景
中斷一個線程意味著在完成其任務(wù)以前,停止線程正在進(jìn)行的工作,即有效的中止當(dāng)前操作。線程中斷后是等待新的任務(wù)還是繼續(xù)進(jìn)行下一步操作將取決于應(yīng)用程序。
盡管在最初看起來比較簡單,你還是需要預(yù)先采取一些措施以求獲得理想的結(jié)果。這里就你必須注意的問題提出了一些建議:
首先,不要使用Thread.stop方法。盡管它的確可以中止一個正在運(yùn)行的線程,但這樣的方法并不安全,并遭到了開發(fā)人員普遍的反對。這也可能意味著在未來的Java版本中它可能不會出現(xiàn)。
另一種并不建議的方法是Thread.interrupt。有人可能會將其與上文提到的方法相混淆。不論它的名字表示什么,這種方法事實(shí)上并沒有立即中斷一個正在運(yùn)行的線程(后來也不會),如列表A所示。它創(chuàng)建了一個線程,并且嘗試使用Thread.interrupt來停止此線程。對Thread.sleep()的調(diào)用提供了充裕的時間來進(jìn)行線程的初始化和結(jié)束。線程本身并沒有做任何有用的事情。
如果運(yùn)行列表A中的代碼,在控制臺中你可以看到類似的如下內(nèi)容:
Starting thread...
Thread is running...
Thread is running...
Thread is running...
Interrupting thread...
Thread is running...
Thread is running...
Thread is running...
Stopping application...
即使在調(diào)用Thread.interrupt()之后,線程還是運(yùn)行了一段時間。
真正的中斷一個線程
中斷一個線程的最好的推薦方法是使用一個共享變量來指示線程必須中止目前所做的工作。線程必須周期性的對變量進(jìn)行檢查,尤其在處理較長的操作的時候,然后通過有序的方式中止線程任務(wù)。列表B的代碼給出了此技術(shù)的具體實(shí)現(xiàn):
運(yùn)行列表B中的代碼將會產(chǎn)生如下輸出(注意線程在前一種方法中是如何退出的):
Starting thread...
Thread is running...
Thread is running...
Thread is running...
Asking thread to stop...
Thread exiting under request...
Stopping application...
盡管這樣的方法需要編寫一定量代碼,但這并不會給執(zhí)行這些線程以及根據(jù)需要對線程進(jìn)行清除帶來多大麻煩,而尤其是清除線程對于任何一個多線程的應(yīng)用程序來說都是絕對必需的。只需要確保已經(jīng)對共享變量聲明為可變,或者將任何對其的訪問封裝在同步代碼塊或方法里面。
到現(xiàn)在為止,一切都很順利。但如果線程被封鎖以等待一些事件,那么將會發(fā)生些什么?當(dāng)然,如果線程被封鎖,它將不能對共享變量進(jìn)行檢查,從而無法停止。有很多時候會發(fā)生這樣的情況,諸如對Object.wait()、ServerSocket.accept()以及DatagramSocket.receive()瞪函數(shù)進(jìn)行調(diào)用的時候。
這些函數(shù)都能夠?qū)⒕程永遠(yuǎn)的封鎖起來。即使采用了超時機(jī)制,或許也不會可行,或者讓人無法忍受線程一直運(yùn)行直到到達(dá)超時狀態(tài)。所以必須采用某種機(jī)制以使線程提早的退出封鎖狀態(tài)。
不幸的是,這里還沒有這樣的機(jī)制能夠適用于所有的情況。但可以根據(jù)具體的情況來使用一些特定的技巧。在如下的部分,我將給出對于絕大部分的常見情況所采取的解決方案。
通過Thread.interrupt()中斷一個線程
如列表A所示,采用Thread.interrupt()方法并沒有中斷一個正在運(yùn)行的線程。此方法事實(shí)上做的只是如果線程被封鎖則拋出一個中斷信號,由此線程退出了封鎖狀態(tài)。更為精確的講,如果線程被封鎖在方法Object.wait、Thread.join或是Thread.sleep,它將接收一個InterruptedException,從而提前終結(jié)封鎖方法。
因此,如果一個線程被封鎖在上述方法中的任意一個,停止它的正確方法是設(shè)置共享變量,并對其調(diào)用interrupt()方法(注意首先設(shè)置變量非常重要)。如果線程沒有被鎖定,調(diào)用interrupt()則無關(guān)緊要,否則,線程將會得到一個異常(線程本身必須準(zhǔn)備好處理這種情況)然后退出鎖定狀態(tài)。在任何一種情況中,最終線程都將會檢測共享變量并終止。列表C的簡單范例程序表明了這一技術(shù)的運(yùn)用。
一旦Thread.interrupt()在列表C代碼中得到調(diào)用,線程將獲得一個異常,于是它退出了封鎖狀態(tài)并決定它應(yīng)當(dāng)停止。運(yùn)行這些代碼將輸出一下結(jié)果:
Starting thread...
Thread running...
Thread running...
Thread running...
Asking thread to stop...
Thread interrupted...
Thread exiting under request...
Stopping application...
對I/O操作進(jìn)行中斷
但如果線程是在執(zhí)行I/O操作時被封鎖將如何解決?I/O可以在一段可觀的時間里保持對一個線程的封鎖,尤其涉及網(wǎng)絡(luò)通信的時候。比如,一個服務(wù)器可能會等待用戶請求,或者一個網(wǎng)絡(luò)應(yīng)用程序會等待遠(yuǎn)程主機(jī)的響應(yīng)。
如果你正在使用通道——在Java 1.4中可以獲得的新的I/O API——封鎖的線程將會得到一個ClosedByInterruptException異常。如果是這種情況,處理的邏輯方法與在第三個例子中所使用的相同——不同的僅僅是產(chǎn)生的異常有所區(qū)別。
但是,由于新的I/O最近才發(fā)布并且還有待進(jìn)一步研究,你也可能還在使用Java 1.0以來一直提供的傳統(tǒng)的I/O。這種情況下,使用Thread.interrupt()不會產(chǎn)生任何幫助,原因是線程將不會退出封鎖狀態(tài)。列表D中的代碼顯示了這一過程。盡管調(diào)用了interrupt()方法,線程還是沒有退出封鎖狀態(tài)。
值得慶幸的是,Java Platform提供了在這種情況下的解決方案:通過調(diào)用鎖定線程的socket的close()方法。這樣的情況下,如果線程是在進(jìn)行I/O操作時被鎖定,線程將得到一個SocketException異常,這非常類似于interrupt()方法引發(fā)了InterruptedException異常的情況。
唯一需要提醒的地方是對socket的引用必須可用,這樣的話close()方法才能被調(diào)用。這也意味著socket對象也必須被共享。列表E顯示了這種情況。處理的邏輯過程與以前給出的范例相同。
運(yùn)行列表E中代碼將會得到如下預(yù)期結(jié)果:
Starting thread...
Waiting for connection...
Asking thread to stop...
accept() failed or interrupted...
Thread exiting under request...
Stopping application...
多線程是功能非常強(qiáng)大的工具,但也給自身的處理帶來了一些列挑戰(zhàn)。其中之一便是如何中斷一個正在運(yùn)行的線程。如果處理得當(dāng),使用這些技術(shù)中斷線程的難度將不會大于使用Java Platform提供的內(nèi)建操作完成同樣的任務(wù)。
列表A:通過Thread.interrupt()中斷線程
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 );
}
public void run() {
while ( true ) {
System.out.println( "Thread is running..." );
long time = System.currentTimeMillis();
while ( System.currentTimeMillis()-time < 1000 ) {
}
}
}
}
列表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..." );
}
}
列表C:通過Thread.interrupt()退出封鎖狀態(tài)
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..." );
}
}
列表D:通過Thread.interrupt()中斷I/O操作
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..." );
}
}
}
}
列表E:I/O操作失敗
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..." );
}
}