終止線程的三種方法
有三種方法可使終止線程。
1. 使用退出標誌,使線程正常退出,也就是當run方法完成後線程終止。
2. 使用stop方法強行終止線程(這個方法不推薦使用,由於stop和suspend、resume同樣,也可能發生不可預料的結果)。
3. 使用interrupt方法中斷線程。
1. 使用退出標誌終止線程
當run方法執行完後,線程就會退出。但有時run方法是永遠不會結束的。如在服務端程序中使用線程進行監聽客戶端請求,或是其餘的須要循環處理的任務。在這種狀況下,通常是將這些任務放在一個循環中,如while循環。若是想讓循環永遠運行下去,可使用while(true){……}來處理。但要想使while循環在某一特定條件下退出,最直接的方法就是設一個boolean類型的標誌,並經過設置這個標誌爲true或false來控制while循環是否退出。下面給出了一個利用退出標誌終止線程的例子。
package chapter2;
public class ThreadFlag extends Thread
{
public volatile boolean exit = false;
public void run()
{
while (!exit);
}
public static void main(String[] args) throws Exception
{
ThreadFlag thread = new ThreadFlag();
thread.start();
sleep(5000); // 主線程延遲5秒
thread.exit = true; // 終止線程thread
thread.join();
System.out.println("線程退出!");
}
}
在上面代碼中定義了一個退出標誌exit,當exit爲true時,while循環退出,exit的默認值爲false.在定義exit時,使用了一個Java關鍵字volatile,這個關鍵字的目的是使exit同步,也就是說在同一時刻只能由一個線程來修改exit的值,
2. 使用stop方法終止線程
使用stop方法能夠強行終止正在運行或掛起的線程。咱們可使用以下的代碼來終止線程:
thread.stop();
雖然使用上面的代碼能夠終止線程,但使用stop方法是很危險的,就象忽然關閉計算機電源,而不是按正常程序關機同樣,可能會產生不可預料的結果,所以,並不推薦使用stop方法來終止線程。
3. 使用interrupt方法終止線程
使用interrupt方法來終端線程可分爲兩種狀況:
(1)線程處於阻塞狀態,如使用了sleep方法。
(2)使用while(!isInterrupted()){……}來判斷線程是否被中斷。
在第一種狀況下使用interrupt方法,sleep方法將拋出一個InterruptedException例外,而在第二種狀況下線程將直接退出。下面的代碼演示了在第一種狀況下使用interrupt方法。
package chapter2;
public class ThreadInterrupt extends Thread
{
public void run()
{
try
{
sleep(50000); // 延遲50秒
}
catch (InterruptedException e)
{
System.out.println(e.getMessage());
}
}
public static void main(String[] args) throws Exception
{
Thread thread = new ThreadInterrupt();
thread.start();
System.out.println("在50秒以內按任意鍵中斷線程!");
System.in.read();
thread.interrupt();
thread.join();
System.out.println("線程已經退出!");
}
}
上面代碼的運行結果以下:
在50秒以內按任意鍵中斷線程!
sleep interrupted
線程已經退出!
在調用interrupt方法後, sleep方法拋出異常,而後輸出錯誤信息:sleep interrupted.
注意:在Thread類中有兩個方法能夠判斷線程是否經過interrupt方法被終止。一個是靜態的方法interrupted(),一個是非靜態的方法isInterrupted(),這兩個方法的區別是interrupted用來判斷當前線是否被中斷,而isInterrupted能夠用來判斷其餘線程是否被中斷。所以,while (!isInterrupted())也能夠換成while (!Thread.interrupted())。
html
如何中止java的線程一直是一個困惱咱們開發多線程程序的一個問題。這個問題最終在Java5的java.util.concurrent中獲得了回答:使用interrupt(),讓線程在run方法中中止。java
在Java的多線程編程中,java.lang.Thread類型包含了一些列的方法start(), stop(), stop(Throwable) and suspend(), destroy() and resume()。經過這些方法,咱們能夠對線程進行方便的操做,可是這些方法中,只有start()方法獲得了保留。編程
在Sun公司的一篇文章《Why are Thread.stop, Thread.suspend and Thread.resume Deprecated? 》中詳細講解了捨棄這些方法的緣由。那麼,咱們究竟應該如何中止線程呢?api
在《Why are Thread.stop, Thread.suspend and Thread.resume Deprecated? 》中,建議使用以下的方法來中止線程:緩存
private volatile Thread blinker;
public void stop() {
blinker = null;
}
public void run() {
Thread thisThread = Thread.currentThread();
while (blinker == thisThread) {
try {
thisThread.sleep(interval);
} catch (InterruptedException e){
}
repaint();
}
}安全
關於使用volatile關鍵字的緣由,請查看http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#36930。網絡
當線程處於下面的情況時,屬於非運行狀態:多線程
當sleep方法被調用。框架
當wait方法被調用。socket
當被I/O阻塞,多是文件或者網絡等等。
當線程處於上述的狀態時,使用前面介紹的方法就不可用了。這個時候,咱們可使用interrupt()來打破阻塞的狀況,如:
public void stop() { Thread tmpBlinker = blinker; blinker = null; if (tmpBlinker != null) { tmpBlinker.interrupt(); } }
當interrupt()被調用的時候,InterruptedException將被拋出,因此你能夠再run方法中捕獲這個異常,讓線程安全退出:
try { .... wait(); } catch (InterruptedException iex) { throw new RuntimeException("Interrupted",iex); }
當線程被I/O阻塞的時候,調用interrupt()的狀況是依賴與實際運行的平臺的。在Solaris和Linux平臺上將會拋出InterruptedIOException的異常,可是Windows上面不會有這種異常。因此,咱們處理這種問題不能依靠於平臺的實現。如:
package com.cnblogs.gpcuster import java.net.*; import java.io.*; public abstract class InterruptibleReader extends Thread { private Object lock = new Object( ); private InputStream is; private boolean done; private int buflen; protected void processData(byte[] b, int n) { } class ReaderClass extends Thread { public void run( ) { byte[] b = new byte[buflen]; while (!done) { try { int n = is.read(b, 0, buflen); processData(b, n); } catch (IOException ioe) { done = true; } } synchronized(lock) { lock.notify( ); } } } public InterruptibleReader(InputStream is) { this(is, 512); } public InterruptibleReader(InputStream is, int len) { this.is = is; buflen = len; } public void run( ) { ReaderClass rc = new ReaderClass( ); synchronized(lock) { rc.start( ); while (!done) { try { lock.wait( ); } catch (InterruptedException ie) { done = true; rc.interrupt( ); try { is.close( ); } catch (IOException ioe) {} } } } } }
另外,咱們也可使用InterruptibleChannel接口。 實現了InterruptibleChannel接口的類能夠在阻塞的時候拋出ClosedByInterruptException
。如:
package com.cnblogs.gpcuster import java.io.BufferedReader; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.channels.Channels; public class InterruptInput { static BufferedReader in = new BufferedReader( new InputStreamReader( Channels.newInputStream( (new FileInputStream(FileDescriptor.in)).getChannel()))); public static void main(String args[]) { try { System.out.println("Enter lines of input (user ctrl+Z Enter to terminate):"); System.out.println("(Input thread will be interrupted in 10 sec.)"); // interrupt input in 10 sec (new TimeOut()).start(); String line = null; while ((line = in.readLine()) != null) { System.out.println("Read line:'"+line+"'"); } } catch (Exception ex) { System.out.println(ex.toString()); // printStackTrace(); } } public static class TimeOut extends Thread { int sleepTime = 10000; Thread threadToInterrupt = null; public TimeOut() { // interrupt thread that creates this TimeOut. threadToInterrupt = Thread.currentThread(); setDaemon(true); } public void run() { try { sleep(10000); // wait 10 sec } catch(InterruptedException ex) {/*ignore*/} threadToInterrupt.interrupt(); } } }
這裏還須要注意一點,當線程處於寫文件的狀態時,調用interrupt()不會中斷線程。
How to Stop a Thread or a Task
Why are Thread.stop, Thread.suspend and Thread.resume Deprecated?
不提倡的stop()方法
臭名昭著的stop()中止線程的方法已不提倡使用了,緣由是什麼呢?
當在一個線程對象上調用stop()方法時,這個線程對象所運行的線程就會當即中止,並拋出特殊的ThreadDeath()異常。這裏的「當即」由於太「當即」了,
假如一個線程正在執行:
synchronized void { x = 3; y = 4; }
因爲方法是同步的,多個線程訪問時總能保證x,y被同時賦值,而若是一個線程正在執行到x = 3;時,被調用了 stop()方法,即便在同步塊中,它也乾脆地stop了,這樣就產生了不完整的殘廢數據。而多線程編程中最最基礎的條件要保證數據的完整性,因此請忘記 線程的stop方法,之後咱們不再要說「中止線程」了。
如何才能「結束」一個線程?
interupt()中斷線程
while(!isInterrupted()){ 正常邏輯 }
public void run(){ while(!isInterrupted()){ try{ 正常工做 }catch(InterruptedException e){ //nothing } } } }想想,若是一個正在sleep的線程,在調用interrupt後,會如何?wait方法檢查到isInterrupted()爲true,拋出異常, 而你又沒有處理。而一個拋出了InterruptedException的線程的狀態立刻就會被置爲非中斷狀態,若是catch語句沒有處理異常,則下一 次循環中isInterrupted()爲false,線程會繼續執行,可能你N次拋出異常,也沒法讓線程中止。
在java多線程編程中,線程的終止能夠說是一個必然會遇到的操做。可是這樣一個常見的操做其實並非一個可以垂手可得實現的操做,並且在某些場景下狀況會變得更復雜更棘手。
Java標準API中的Thread類提供了stop方法能夠終止線程,可是很遺憾,這種方法不建議使用,緣由是這種方式終止線程中斷臨界區代碼執行,並會釋放線程以前獲取的監控器鎖,這樣勢必引發某些對象狀態的不一致(由於臨界區代碼通常是原子的,不會被幹擾的),具體緣由能夠參考資料[1]。這樣一來,就必須根據線程的特色使用不一樣的替代方案以終止線程。根據中止線程時線程執行狀態的不一樣有以下中止線程的方法。
處於運行狀態的線程就是常見的處於一個循環中不斷執行業務流程的線程,這樣的線程須要經過設置中止變量的方式,在每次循環開始處判斷變量是否改變爲中止,以達到中止線程的目的,好比以下代碼框架:
若是主線程調用該線程對象的stop方法,blinker對象被設置爲null,則線程的下次循環中blinker!=thisThread,於是能夠退出循環,並退出run方法而使線程結束。將引用變量blinker的類型前加上volatile關鍵字的目的是防止編譯器對該變量存取時的優化,這種優化主要是緩存對變量的修改,這將使其餘線程不會馬上看到修改後的blinker值,從而影響退出。此外,Java標準保證被volatile修飾的變量的讀寫都是原子的。
上述的Thread類型的blinker徹底能夠由更爲簡單的boolean類型變量代替。
線程的非運行狀態常見的有以下兩種狀況:
可中斷等待:線程調用了sleep或wait方法,這些方法可拋出InterruptedException;
Io阻塞:線程調用了IO的read操做或者socket的accept操做,處於阻塞狀態。
若是線程調用了可中斷等待方法,正處於等待狀態,則能夠經過調用Thread的interrupt方法讓等待方法拋出InterruptedException異常,而後在循環外截獲並處理異常,這樣便跳出了線程run方法中的循環,以使線程順利結束。
上述的stop方法中須要作的修改就是在設置中止變量以後調用interrupt方法:
特別的,Thread對象的interrupt方法會設置線程的interruptedFlag,因此咱們能夠經過判斷Thread對象的isInterrupted方法的返回值來判斷是否應該繼續run方法內的循環,從而代替線程中的volatile中止變量。這時的上述run方法的代碼框架就變爲以下:
須要注意的是Thread對象的isInterrupted不會清除interrupted標記,可是Thread對象的interrupted方法(與interrupt方法區別)會清除該標記。
Java中的輸入輸出流並無相似於Interrupt的機制,可是Java的InterruptableChanel接口提供了這樣的機制,任何實現了InterruptableChanel接口的類的IO阻塞都是可中斷的,中斷時拋出ClosedByInterruptedException,也是由Thread對象調用Interrupt方法完成中斷調用。IO中斷後將關閉通道。
以文件IO爲例,構造一個可中斷的文件輸入流的代碼以下:
實現InterruptableChanel接口的類包括FileChannel,ServerSocketChannel, SocketChannel, Pipe.SinkChannel andPipe.SourceChannel,也就是說,原則上能夠實現文件、Socket、管道的可中斷IO阻塞操做。
雖然解除IO阻塞的方法還能夠直接調用IO對象的Close方法,這也會拋出IO異常。可是InterruptableChanel機制可以使處於IO阻塞的線程可以有一個和處於中斷等待的線程一致的線程中止方案。
處於大數據IO讀寫中的線程實際上處於運行狀態,而不是等待或阻塞狀態,所以上面的interrupt機制不適用。線程處於IO讀寫中能夠當作是線程運行中的一種特例。中止這樣的線程的辦法是強行close掉io輸入輸出流對象,使其拋出異常,進而使線程中止。
最好的建議是將大數據的IO讀寫操做放在循環中進行,這樣能夠在每次循環中都有線程中止的時機,這也就將問題轉化爲如何中止正在運行中的線程的問題了。
有時,線程中的run方法須要足夠健壯以支持在線程實際運行前終止線程的狀況。即在Thread建立後,到Thread的start方法調用前這段時間,調用自定義的stop方法也要奏效。從上述的中止處於等待狀態線程的代碼示例中,stop方法並不能終止運行前的線程,由於在Thread的start方法被調用前,調用interrupt方法並不會將Thread對象的中斷狀態置位,這樣當run方法執行時,currentThread的isInterrupted方法返回false,線程將繼續執行下去。
爲了解決這個問題,不得不本身再額外建立一個volatile標誌量,並將其加入run方法的最開頭:
還有一種解決方法,也能夠在run中直接使用該自定義標誌量,而不使用isInterrupted方法判斷線程是否應該中止。這種方法的run代碼框架實際上和中止運行時線程的同樣。