停止(终止)线程
停止線程是在多線程開發中很重要的技術點,掌握此技術可以對線程的停止進行有效的處理。停止線程在 Java 語言中并不像 break 語句那樣干脆,需要一些技巧性的處理。
使用 Java 內置支持多線程的類設計多線程應用是很常見的事情,然而,多線程給開發人員帶來了一些新的挑戰,如果處理不好就會導致超出預期的行為并且難以定位錯誤。
本節將討論如何更好地停止一個線程。停止一個線程意味著在線程處理完任務之前停掉正在做的操作,也就是放棄當前的操作。雖然這看起來非常簡單,但是必須做好防范措施,以便達到預期的效果。
停止一個線程可以使用 Threadstop() 方法,但最好不用它。雖然它確實可以停止一個正在運行的線程,但是這個方法是不安全的,而且已被棄用作廢了,在將來的 Java 版本中,這個方法將不可用或不被支持。
大多數停止一個線程的操作使用 Thread.interrupt() 方法,盡管方法的名稱是“停止,中止”的意思,但這個方法不會終止一個正在運行的線程,還需要加入一個判斷才可以完成線程的停止。關于此知識點在后面有專門的章節進行介紹。
在 Java 中有以下 3 種方法可以終止正在運行的線程:
停止不了的線程
interrupt() 方法的作用是用來停止線程,但 intermpt() 方法的使用效果并不像循環結構中 break 語句那樣,可以馬上停止循環。調用 intermpt() 方法僅僅是在當前線程中打了一個停止的標記,并不是真的停止線程。
例 1
下面通過一個案例演示 interrupt() 方法停止線程的用法。案例用到的線程非常簡單,僅僅是實現輸出從 1~10000 的整數,代碼如下:
在調用 intermpt() 方法停止 MyThread 線程之前,首先進行了一個 100 毫秒的休眠。主線程的代碼如下:
public class Test {public static void main(String[] args) {try {MyThread thread=new MyThread();//創建MyThread線程實例thread.start();//啟動線程Thread.sleep(100);thread.interrupt();//停止線程} catch (InterruptedException e) {System.out.println("main catch");e.printStackTrace();}//延時100毫秒} }主線程的運行結果如下所示。從中可以看到,雖然在延時 100 毫秒后調用 intermpt() 方法停止了 thread 線程,但是該線程仍然執行完成輸出 10000 行信息。
i=1 i=2 ... i=9999 i=10000判斷線程是不是停止狀態
在介紹如何停止線程的知識點前,先來看一下如何判斷線程的狀態是不是停止的。在 Java 的 SDK 中,Thread.java 類里提供了兩種方法。
那么這兩個方法有什么區別呢?先來看看 this.intermpted() 方法的解釋:測試當前線程是否已經中斷,當前線程是指運行 this.interrupted() 方法的線程。為了對此方法有更深入的了解,下面通過一個案例進行說明。
例 2
假設 MyThread 線程類的代碼如下:
主線程的代碼如下:
public class Test {public static void main(String[] args) {try {MyThread thread=new MyThread();//創建MyThread線程實例thread.start();//啟動線程Thread.sleep(100); //延時100毫秒thread.interrupt();//停止線程System.out.println("是否停止1?="+thread.interrupted());System.out.println("是否停止2?="+thread.interrupted());} catch (InterruptedException e) {System.out.println("main catch");e.printStackTrace();}System.out.println("end!");} }程序運行后的結果如下所示。
i=1 i=2 ... i=9999 i=10000 是否停止1?=false 是否停止2?=false end!在主線程中是在 thread 對象上調用以下代碼來停止 thread 對象所代表的線程。
thread.interrupt();后面又使用以下代碼來判斷 thread 對象所代表的線程是否停止。
System.out.println("是否停止 1 ? ="+thread.interrupted()); System.out.println("是否停止 2 ? ="+thread.interrupted());從控制臺打印的結果來看,線程并未停止,這也就證明了 interrupted() 方法的解釋:測試當前線程是否已經中斷。這個“當前線程”是 main,它從未中斷過,所以打印的結果是兩個 false。
那么如何使 main 線程產生中斷效果呢?再來看一下如下的代碼:
public static void main(String[] args) {Thread.currentThread().interrupt();System.out.println(" 是否停止 1 ? ="+Thread.interrupted());System.out.println(" 是否停止 2 ? ="+Thread.interrupted());System.out.println("end!"); }程序運行后的結果如下所示。
是否停止 1 ? =true 是否停止 2 ? =false end!從上述的結果來看,intermpted() 方法的確用來判斷出當前線程是不是停止狀態。但為什么第二個布爾值是 false 呢?查看一下官方幫助文檔中對 interrupted() 方法的解釋如下(斜體顯示):
測試當前線程是否已經中斷。線程的中斷狀態由該方法清除。換句話說,如果連續兩次調用該方法,則第二次調用將返回 false(在第一次調用已清除了其中斷狀態之后,且第二次調用檢驗完中斷狀態前,當前線程再次中斷的情況除外)。
文檔已經解釋得很詳細,intermpted() 方法具有清除狀態的功能,所以第二次調用 interrupted() 方法返回的值是 false。
isInterrupted() 方法
介紹完 interrupted() 方法后再來看一下 isInterrupted() 方法。isInterrupted() 方法的聲明如下:
從聲明中可以看出 isIntermpted() 方法不是 static 的。使用 isInterrupted() 方法來判斷線程是否停止,具體代碼如下:
public class Test {public static void main(String[] args){try{MyThread thread=new MyThread();thread.start();Thread.sleep(100);thread.interrupt();System.out.println("是否停止1?="+thread.isInterrupted());System.out.println("是否停止2?="+thread.isInterrupted());}catch(InterruptedException e){System.out.println("main catch");e.printStackTrace();}System.out.println("end!");} }程序運行結果如下所示。
i=498 是否停止1?=true i=499 是否俜止2?=true i=500 end! i=501 i=502從程序的運行結果中可以看到,isInterrupted() 方法并未清除狀態標識,所以打印了兩個 true。經過上面示例的驗證總結一下這兩個方法。
- this.interrupted():測試當前線程是否已經是中斷狀態,執行后具有將狀態標識清除為 false 的功能。
- this.islnterrupted():測試線程 Thread 對象是否已經是中斷狀態,但不清除狀態標識。
異常法停止線程
有了前面學習過的知識,就可在線程中用 for 語句來判斷線程是否為停止狀態,如果是停止狀態,則后面的代碼不再運行。
例 3
下面的線程類 MyThread 演示了在線程中使用 for 循環,并在循環中調用 intermpted() 方法判斷線程是否停止。
上述代碼啟動 MyThread 線程后延時 2000 毫秒,之后將線程停止。為避免主線程崩潰使用 catch 捕捉了 InterruptedException 異常,此時會輸出“main catch”。在主線程執行結束后會輸出“end!”。程序執行的輸出結果如下所示。
...... i=271597 i=271598 已經是停止狀態了!我要退出了! end!從程序執行的結果可以看到,在示例中雖然停止了線程,但如果 for 語句下面還有語句,還是會繼續運行的。
下面對 MyThread線程進行修改,如下所示是 run() 方法的代碼:
public void run() { super.run(); for(int i=0;i<500000;i++){ if(this.interrupted()){ System.out.println("已經是停止狀態了!我要退出了!"); break; } System.out.println("i="+(i+1)); } System.out.println("我被輸出,如果此代碼是for又繼續運行,線程并未停止!"); }此時的運行效果如下所示,說明線程仍然在繼續運行。
...... i=233702 i=233703 end! 已經是停止狀態了!我要退出了! 我被輸出,如果此代碼是for又繼續運行,線程并未停止!那該如何解決線程停止后,語句仍然繼續運行的問題呢?解決的辦法是在線程中捕捉線程停止異常,如下為修改后的 run() 方法代碼。
public void run() { super.run(); try{ for(int i=0;i<500000;i++){ if(this.interrupted()){ System.out.println("已經是停止狀態了!我要退出了!"); throw new InterruptedException(); } System.out.println("i=" + (i + 1)); } System.out.println("我在for下面"); }catch(InterruptedException e){ System.out.println("進MyThread15.java類run方法中的catch了!"); e.printStackTrace(); } }再次運行程序,當線程處于停止狀態后,如果 for 循環中的代碼繼續執行將會拋出 InterruptedException 異常,運行結果如下所示。
...... i=251711 i=251712 i=251713 已經是停止狀態了!我要退出了! end! 進MyThread15.java類run方法中的catch了! java.lang.InterruptedExceptionat text.MyThread.run(MyThread.java:16)在休眠中停止
如果線程在 sleep() 狀態下停止,會是什么效果呢?
例 4
下面通過一個案例來演示這種情況。
在上述代碼中啟動 MyThread16 線程后休眠了 200 毫秒,之后調用 interrupt() 方法停止線程,運行結果如下所示。
run begin end! 在休眠中被停止!進入catch!false java.lang.InterruptedException: sleep interruptedat java.lang.Thread.sleep(Native Method)at text.MyThread.run(MyThread.java:12)從運行結果來看,如果在休眠狀態下停止某一線程則會拋出進入 InterruptedException 異常,所以會進入 catch 語句塊清除停止狀態值,使之變成 false。
例 5
這個示例是先休眠再停止線程,下面再編寫一個案例來演示先停止再休眠線程的情況:
在上述代碼中啟動 MyThread 線程后沒有進行延時,馬上調用 interrupt() 方法進行停止線程,但是在 MyThread線程中有一個 200 毫秒的延時。運行程序后,首先會看到下所示的輸出,說明主線程執行完畢。
end! i=1 i=2 i=3 i=4 i=5 i=6 ......稍等片刻后,將會看到如下所示的異常,說明線程停止了。
...... i=999 i=1000 run begin 先停止,再遇到了sleep!進入catch! java.lang.InterruptedException: sleep interruptedat java.lang.Thread.sleep(Native Method)at text.MyThread.run(MyThread.java:16)強制停止線程
調用 stop() 方法可以在任意情況下強制停止一個線程。下面通過一個案例來演示 stop() 停止線程的方法。
public class MyThread extends Thread {private int i=0;@Overridepublic void run(){try{while (true){i++;System.out.println("i=" + i);Thread.sleep(1000);}}catch(InterruptedException e){//TODO Auto-generated catch blocke.printStackTrace();}} }如上述代碼所示,MyThread線程中包含一個死循環,該循環每隔 1000 毫秒執行一次,每次將 i 的值遞增 1 并輸出。
調用 MyThread線程的主線程代碼如下:
public class Test {@SuppressWarnings("deprecation")public static void main(String[] args){try{MyThread thread=new MyThread();thread.start();Thread.sleep(8000);thread.stop();}catch(InterruptedException e){// TODO Auto-generated catch blocke.printStackTrace();}} }如上述代碼所示,MyThread 線程在啟動后有一個 8000 毫秒的延時,在這段時間內會循環 9 次,之后 stop() 方法被執行從而線程停止。運行后的輸出如下所示。
i=1 i=2 i=3 i=4 i=5 i=6 i=7 i=8 i=9注意:調用 stop() 方法時會拋出 java.lang.ThreadDeath 異常,但在通常情況下,此異常不需要顯式地捕捉。
釋放鎖的不良后果
從 JDK 1.6 以后 stop() 方法已經被作廢,因為如果強制讓線程停止則有可能使一些清理性的工作得不到完成。另外一個情況就是對鎖定的對象進行了“解鎖”,導致數據得不到同步的處理,出現數據不一致的問題。
使用 stop() 釋放鎖將會給數據造成不一致性的結果。如果出現這樣的情況,程序處理的數據就有可能遭到破壞,最終導致程序執行的流程錯誤,一定要特別注意。
例 6
下面通過一個案例來演示這種情況。案例使用了一個名為 SynchronizedObject 的實體類,該類代碼如下:
如上述代碼所示,SynchronizedObject 類包含用戶名和密碼兩個成員,printString() 方法用于對這兩個成員進行賦值,但是在設置密碼之前有一個休眠時間。
下面編寫一個線程來對 SynchronizedObject 類進行實例化,并調用 printString() 方法。線程代碼如下:
public class MyThread extends Thread {private SynchronizedObject object;public MyThread(SynchronizedObject object){super();this.object=object;}@Overridepublic void run(){object.printString("admin", "1234");} } public class Test {public static void main(String[] args){try{SynchronizedObject object=new SynchronizedObject();MyThread thread=new MyThread(object);thread.start();thread.sleep(500);thread.stop();System.out.println("用戶名:"+object.getUsername());System.out.println("密碼:"+object.getPassword());}catch(Exception e){e.printStackTrace();}}}在上述代碼中創建一個 SynchronizedObject 類實例,并將該實例作為參數傳遞給 MyThread線程。MyThread 線程啟動后將立即調用 object.printString('fadminn,"123456") 方法,而在 printString() 方法內有一個較長時間的休眠。該休眠時間大于主線程的休眠時間,所以主線程會繼續往下執行,當執行到 stop() 方法時線程被強制停止。
程序最后的運行結果如下所示。
用戶名:admin 密碼:root由于 stop() 方法已經在中被標明是“作廢/過期”的方法,顯然它在功能上具有缺陷,所以不建議在程序中使用 stop() 方法。
使用 return 停止線程
除了上面介紹的方法外,還可以將 intermpt() 方法與 return 結合使用來實現停止線程的效果。
例 7
下面通過一個案例來演示這種情況。如下所示為案例中使用 MyThread 線程類的代碼。
調用 MyThread 線程的主線程代碼如下:
public class Test {public static void main(String[] args) throws InterruptedException{MyThread t=new MyThread();t.start();Thread.sleep(2000);t.interrupt();} }程序執行后的結果如下所示。
...... timer=1540977194784 timer=1540977194784 timer=1540977194784 timer=1540977194784 timer=1540977194784 停止了!從程序的執行結果中可以看到成功停止了線程,不過還是建議使用“拋異常”的方法來實現線程的停止,因為在 catch 塊中還可以將異常向上拋,使線程停止的事件得以傳播。
總結