在上篇文章《多線程的使用——Thread類和Runnable接口》中提到中斷線程的問題。在JAVA中,曾經使用stop方法來中止線程,然而,該方法具備固有的不安全性,於是已經被拋棄(Deprecated)。那麼應該怎麼結束一個進程呢?官方文檔中對此有詳細說明:《爲什麼不同意使用 Thread.stop、Thread.suspend 和 Thread.resume?》。在此引用stop方法的說明:html
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的值。java
package com.jvm.study.thread; public class TestThread implements Runnable{ boolean stop = false; public static void main(String[] args) throws Exception { Thread thread = new Thread(new TestThread(),"My Thread"); System.out.println( "Starting thread..." ); thread.start(); Thread.sleep( 3000 ); System.out.println( "Interrupting thread..." ); thread.interrupt(); System.out.println("線程是否中斷:" + thread.isInterrupted()); Thread.sleep( 3000 ); System.out.println("Stopping application..." ); } public void run() { while(!stop){ System.out.println( "My Thread is running..." ); // 讓該循環持續一段時間,使上面的話打印次數少點 long time = System.currentTimeMillis(); while((System.currentTimeMillis()-time < 1000)) { } } System.out.println("My Thread exiting under request..." ); } }
package com.jvm.study.thread; public class TestThread2 implements Runnable { boolean stop = false; public static void main(String[] args) throws Exception { Thread thread = new Thread(new TestThread2(), "My Thread2"); System.out.println("Starting thread..."); thread.start(); Thread.sleep(10000); System.out.println("Interrupting thread..."); thread.interrupt(); System.out.println("線程是否中斷:" + thread.isInterrupted()); Thread.sleep(3000); System.out.println("Stopping application..."); Thread.sleep(30000); } public void run() { while (!stop) { System.out.println("My Thread is running..."); // 讓該循環持續一段時間,使上面的話打印次數少點 long time = System.currentTimeMillis(); while ((System.currentTimeMillis() - time < 1000)) { } if (Thread.currentThread().isInterrupted()) { return; } } System.out.println("My Thread exiting under request..."); } }
注意:interrupted與isInterrupted方法的區別(見API文檔)程序員
當線程處於下面的Blocked狀態,經過調用interrupt()方法來中止線程:編程
2.一、當線程處於下面的情況時((1)若是線程在調用 Object 類的 wait()、wait(long) 或 wait(long, int) 方法,或者該類的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法過程當中受阻,則其中斷狀態將被清除,它還將收到一個InterruptedException異常。這個時候,咱們能夠經過捕獲InterruptedException異常來終止線程的執行,具體能夠經過return等退出或改變共享變量的值使其退出。),屬於非運行狀態:api
當sleep方法被調用。緩存
當wait方法被調用。安全
當被I/O阻塞,多是文件或者網絡等等。網絡
當線程處於上述的狀態時,使用前面介紹的方法就不可用了。這個時候,咱們可使用interrupt()來打破阻塞的狀況,如:多線程
public void stop() { Thread tmpBlinker = blinker; blinker = null; if (tmpBlinker != null) { tmpBlinker.interrupt(); } }
當interrupt()被調用的時候,InterruptedException將被拋出,因此你能夠再run方法中捕獲這個異常,讓線程安全退出:oracle
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()不會中斷線程。
3、不提倡的stop()方法
臭名昭著的stop()中止線程的方法已不提倡使用了,緣由是什麼呢?
當在一個線程對象上調用stop()方法時,這個線程對象所運行的線程就會當即中止,並拋出特殊的ThreadDeath()異常。這裏的「當即」由於太「當即」了,
在java多線程編程中,線程的終止能夠說是一個必然會遇到的操做。可是這樣一個常見的操做其實並非一個可以垂手可得實現的操做,並且在某些場景下狀況會變得更復雜更棘手。
Java標準API中的Thread類提供了stop方法能夠終止線程,可是很遺憾,這種方法不建議使用,緣由是這種方式終止線程中斷臨界區代碼執行,並會釋放線程以前獲取的監控器鎖,這樣勢必引發某些對象狀態的不一致(由於臨界區代碼通常是原子的,不會被幹擾的),具體緣由能夠參考資料[1]。這樣一來,就必須根據線程的特色使用不一樣的替代方案以終止線程。根據中止線程時線程執行狀態的不一樣有以下中止線程的方法。
處於運行狀態的線程就是常見的處於一個循環中不斷執行業務流程的線程,這樣的線程須要經過設置中止變量的方式,在每次循環開始處判斷變量是否改變爲中止,以達到中止線程的目的,好比以下代碼框架:
private volatile Thread blinker; public void stop() { blinker = null; } public void run() { Thread thisThread = Thread.currentThread(); while (blinker == thisThread) { try { //業務流程 } catch (Exception e){} } }
若是主線程調用該線程對象的stop方法,blinker對象被設置爲null,則線程的下次循環中blinker!=thisThread,於是能夠退出循環,並退出run方法而使線程結束。將引用變量blinker的類型前加上volatile關鍵字的目的是防止編譯器對該變量存取時的優化,這種優化主要是緩存對變量的修改,這將使其餘線程不會馬上看到修改後的blinker值,從而影響退出。此外,Java標準保證被volatile修飾的變量的讀寫都是原子的。
上述的Thread類型的blinker徹底能夠由更爲簡單的boolean類型變量代替。
線程的非運行狀態常見的有以下兩種狀況:
可中斷等待:線程調用了sleep或wait方法,這些方法可拋出InterruptedException;
Io阻塞:線程調用了IO的read操做或者socket的accept操做,處於阻塞狀態。
上面已經講過。
處於大數據IO讀寫中的線程實際上處於運行狀態,而不是等待或阻塞狀態,所以上面的interrupt機制不適用。線程處於IO讀寫中能夠當作是線程運行中的一種特例。中止這樣的線程的辦法是強行close掉io輸入輸出流對象,使其拋出異常,進而使線程中止。
最好的建議是將大數據的IO讀寫操做放在循環中進行,這樣能夠在每次循環中都有線程中止的時機,這也就將問題轉化爲如何中止正在運行中的線程的問題了。
有時,線程中的run方法須要足夠健壯以支持在線程實際運行前終止線程的狀況。即在Thread建立後,到Thread的start方法調用前這段時間,調用自定義的stop方法也要奏效。從上述的中止處於等待狀態線程的代碼示例中,stop方法並不能終止運行前的線程,由於在Thread的start方法被調用前,調用interrupt方法並不會將Thread對象的中斷狀態置位,這樣當run方法執行時,currentThread的isInterrupted方法返回false,線程將繼續執行下去。
爲了解決這個問題,不得不本身再額外建立一個volatile標誌量,並將其加入run方法的最開頭:
public void run() { if (myThread == null) { return; // stopped before started. } while(!Thread.currentThread().isInterrupted()){ //業務邏輯 } }
還有一種解決方法,也能夠在run中直接使用該自定義標誌量,而不使用isInterrupted方法判斷線程是否應該中止。這種方法的run代碼框架實際上和中止運行時線程的同樣。