如何正確的停止一個線程?

何爲終止線程?
停止一個線程意味着在任務處理完任務之前停掉正在做的操作,也就是放棄當前的操作。


終止線程的方法
①使用退出標誌,使線程正常退出,也就是當run方法完成後線程終止。
②使用stop方法強行終止,但是不推薦這個方法,因爲stop和suspend及resume一樣都是過期作廢的方法。
③使用interrupt方法中斷線程。
④使用return返回。


判斷線程是否停止狀態
Thread.java類中提供了兩種方法:
this.interrupted(): 測試當前線程是否已經中斷;
this.isInterrupted(): 測試線程是否已經中斷;
上面兩個方法有一些區別。


使用isInterrupted方法測試線程是否已經中斷
  1. public class Run {
  2. public static void main(String args[]) throws InterruptedException {
  3. Thread thread = new MyThread();
  4. thread.start();
  5. thread.interrupt();
  6. System.out.println("stop 1??" + thread.isInterrupted()); //true
  7. System.out.println("stop 1??" + Thread.currentThread().isInterrupted()); //false
  8. }
  9. }

  1. public class MyThread extends Thread {
  2. public void run(){
  3. super.run();
  4. for(int i=0; i<5000000; i++){
  5. i++;
  6. // System.out.println("i="+(i+1));
  7. }
  8. }
  9. }
上面的代碼中對thread線程進行終止,main線程沒有終止,所以輸出一個true,一個false。
 

停止線程的方法:thread.interrupt()
  1. public class Run {
  2. public static void main(String args[]){
  3. Thread thread = new MyThread();
  4. thread.start();
  5. try {
  6. Thread.sleep(2000);
  7. thread.interrupt();
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. }
  12. }
  13. class MyThread extends Thread {
  14. public void run(){
  15. super.run();
  16. for(int i=0; i<500000; i++){
  17. if(this.interrupted()) {
  18. System.out.println("線程已經終止, for循環不再執行");
  19. break;
  20. }
  21. System.out.println("i="+(i+1));
  22. }
  23. }
  24. }

輸出結果:
i=133837
i=133838
i=133839
i=133840
線程已經終止, for循環不再執行

上面的代碼中,雖然線程如期停止,但是如果在run方法的for循環後面還有語句,這些語句是會被執行的。
執行以下代碼
  1. public class MyThread extends Thread {
  2. public void run(){
  3. super.run();
  4. for(int i=0; i<500000; i++){
  5. if(this.interrupted()) {
  6. System.out.println("線程已經終止, for循環不再執行");
  7. break;
  8. }
  9. System.out.println("i="+(i+1));
  10. }
  11. System.out.println("這是for循環外面的語句,也會被執行");
  12. }
  13. }

執行結果:
i=137682
i=137683
i=137684
線程已經終止, for循環不再執行
這是for循環外面的語句,也會被執行

如何解決上述問題,我們可以在this.interrupted()爲true時,拋出一個異常,通過異常來終止線程繼續向下進行。
  1. public class MyThread extends Thread {
  2. public void run(){
  3. super.run();
  4. try {
  5. for(int i=0; i<500000; i++){
  6. if(this.interrupted()) {
  7. System.out.println("線程已經終止, for循環不再執行");
  8. throw new InterruptedException();
  9. }
  10. System.out.println("i="+(i+1));
  11. }
  12. System.out.println("這是for循環外面的語句,也會被執行");
  13. } catch (InterruptedException e) {
  14. System.out.println("進入MyThread.java類中的catch了。。。");
  15. e.printStackTrace();
  16. }
  17. }
  18. }

執行結果:
i=152395
i=152396
i=152397
線程已經終止, for循環不再執行
進入MyThread.java類中的catch了。。。
java.lang.InterruptedException
at com.coach.javainterview.thread.MyThread.run(Run.java:23)


如果線程在sleep()狀態下interrupt線程,會是什麼效果呢?
  1. public class Run {
  2. public static void main(String args[]){
  3. Thread thread = new MyThread();
  4. thread.start();
  5. try {
  6. Thread.sleep(2000); //main線程先sleep2秒
  7. thread.interrupt(); //對thread線程進行interrupt
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. }
  12. }
  13. class MyThread extends Thread {
  14. public void run(){
  15. super.run();
  16. try {
  17. System.out.println("線程開始。。。");
  18. Thread.sleep(200000);
  19. System.out.println("線程結束。");
  20. } catch (InterruptedException e) {
  21. System.out.println("在沉睡中被停止, 進入catch, 調用isInterrupted()方法的結果是:" + this.isInterrupted());
  22. e.printStackTrace();
  23. }
  24. }
  25. }
執行結果:
線程開始。。。
在沉睡中被停止, 進入catch, 調用isInterrupted()方法的結果是:false
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.coach.javainterview.thread.MyThread.run(Run.java:22)


如果線程在interrupt()狀態下停止線程,會是什麼效果呢?
  1. public class Run {
  2. public static void main(String args[]){
  3. Thread thread = new MyThread();
  4. thread.start();
  5. thread.interrupt();
  6. }
  7. }
  8. class MyThread extends Thread {
  9. public void run(){
  10. super.run();
  11. try {
  12. System.out.println("線程開始。。。");
  13. for(int i=0; i<10000; i++){
  14. System.out.println("i=" + i);
  15. }
  16. Thread.sleep(200000); //interrupt狀態時讓線程sleep
  17. System.out.println("線程結束。");
  18. } catch (InterruptedException e) {
  19. System.out.println("先停止,再遇到sleep,進入catch異常");
  20. e.printStackTrace();
  21. }
  22. }
  23. }

運行結果:
i=9997
i=9998
i=9999
先停止,再遇到sleep,進入catch異常
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.coach.javainterview.thread.MyThread.run(Run.java:20)


停止線程的方法:使用stop方法
  1. public class Run {
  2. public static void main(String args[]) throws InterruptedException {
  3. Thread thread = new MyThread();
  4. thread.start();
  5. Thread.sleep(2000);
  6. thread.stop();
  7. }
  8. }
  9. class MyThread extends Thread {
  10. private int i = 0;
  11. public void run(){
  12. super.run();
  13. try {
  14. while (true){
  15. System.out.println("i=" + i);
  16. i++;
  17. Thread.sleep(200);
  18. }
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. }

執行結果:
i=5
i=6
i=7
i=8
i=9



爲什麼不推薦使用stop方法來停止線程?

原因一:
stop方法是過時的
從Java編碼規則來說,已經過時的方式不建議採用.


原因二:
stop方法會導致代碼邏輯不完整
stop方法是一種"惡意" 的中斷,一旦執行stop方法,即終止當前正在運行的線程,不管線程邏輯是否完整,這是非常危險的.
  1. public class Client {
  2. public static void main(String[] args) throws Exception {
  3. // 子線程
  4. Thread thread = new Thread() {
  5. @Override
  6. public void run() {
  7. try {
  8. // 該線程休眠1秒
  9. Thread.sleep(1000);
  10. } catch (InterruptedException e) {
  11. //異常處理
  12. }
  13. System.out.println("此處代碼不會執行");
  14. }
  15. };
  16. // 啓動線程
  17. thread.start();
  18. // 主線程休眠0.1秒
  19. Thread.sleep(100);
  20. // 子線程停止
  21. thread.stop();
  22. }
  23. }

上面的代碼中,我們讓子線程thread啓動後休眠1秒,在此線程休眠的同時,我們強行使用stop方法來停止thread子線程,在println中可能是要輸出一些很重要的信息,比如子線程的主邏輯,資源回收,情景初始化等等,但是因爲調用了stop方法,後面的println方法不會被調用。
這是極度危險的,因爲我們不知道子線程會在什麼時候停止,stop連基本的邏輯完整性都無法保證,而且此種操作也是非常隱蔽的,子線程執行到何處會被關閉很難定位,這爲以後的維護帶來了很多的麻煩. 


原因三:
stop方法會破壞原子邏輯
  1. public class Client {
  2. public static void main(String[] args) {
  3. MultiThread t = new MultiThread();
  4. Thread t1 = new Thread(t);
  5. // 啓動t1線程
  6. t1.start();
  7. for (int i = 0; i < 5; i++) {
  8. new Thread(t).start();
  9. }
  10. // 停止t1線程
  11. t1.stop();
  12. }
  13. }
  14. class MultiThread implements Runnable {
  15. int a = 0;
  16. @Override
  17. public void run() {
  18. // 同步代碼塊,保證原子操作
  19. synchronized ("") {
  20. // 自增
  21. a++;
  22. try {
  23. // 線程休眠0.1秒
  24. Thread.sleep(100);
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. // 自減
  29. a--;
  30. String tn = Thread.currentThread().getName();
  31. System.out.println(tn + ":a =" + a);
  32. }
  33. }
  34. }

MultiThread實現了Runnable接口,具備多線程的能力,run方法中加入了synchronized代碼塊,表示內部是原子邏輯,a的值會先增加後減少,按照synchronized的規則,無論啓動多少個線程,打印出來的結果都應該是a=0。
但是如果有一個正在執行的線程被stop,就會破壞這種原子邏輯.(上面main方法中代碼)
首先說明的是所有線程共享了 一個MultThread的實例變量t,其次由於在run方法中加入了同步代碼塊,所以只能有一個線程進入到synchronized塊中。

此段代碼的執行順序如下:
1)線程t1啓動,並執行run方法,由於沒有其他線程同步代碼塊的鎖,所以t1線程執行自加後執行到sleep方法開始休眠,此時a=1.

2)JVM又啓動了5個線程,也同時運行run方法,由於synchronized關鍵字的阻塞作用,這5個線程不能執行自增和自減操作,等待t1線程釋放線程鎖.

3)主線程執行了t1.stop方法,終止了t1線程,注意由於a變量是線程共享的,所以其他5個線程獲得的a變量也是1.

4)其他5個線程獲得CPU的執行機會,打印出a的值.

上面代碼的執行結果是:
Thread-4:a =1
Thread-1:a =1
Thread-2:a =1
Thread-3:a =1
Thread-5:a =1


停止線程的方法:使用return
  1. class MyThread extends Thread {
  2. public void run(){
  3. while (true){
  4. if(this.isInterrupted()){
  5. System.out.println("線程被停止了!");
  6. return;
  7. }
  8. System.out.println("Time: " + System.currentTimeMillis());
  9. }
  10. }
  11. }
  12. public class Run {
  13. public static void main(String args[]) throws InterruptedException {
  14. Thread thread = new MyThread();
  15. thread.start();
  16. Thread.sleep(2000);
  17. thread.interrupt();
  18. }
  19. }

執行結果:
Time: 1516708475697
Time: 1516708475697
Time: 1516708475697
Time: 1516708475697
線程被停止了!











發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章