[改善Java代碼]不使用stop方法停止線程


線程啟動完畢后,在運行可能需要終止,Java提供的終止方法只有一個stop,但是不建議使用此方法,因為它有以下三個問題:

(1)stop方法是過時的

從Java編碼規則來說,已經過時的方式不建議采用.

(2)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     }
24 }

 

這段代碼的邏輯,子線程是一個匿名內部類,它的run方法在執行時會休眠1秒鍾,然后再執行后續的邏輯,而主線程則是休眠0.1秒后終止子線程的運行,也就是說,JVM在執行thread.stop()時,子線程還在執行sleep(1000),此時stop方法會清除棧內信息,結束該線程,這也導致了run方法的邏輯不完整,輸出語句println代表的是一段邏輯,可能非常重要,比如子線程的主邏輯,資源回收,情景初始化等等,但是因為stop線程了,這些就都不再執行了,於是就產生了業務邏輯不完整的情況.

這是極度危險的,因為我們不知道子線程會在什么時候停止,stop連基本的邏輯完整性都無法保證,而且此種操作也是非常隱蔽的,子線程執行到何處會被關閉很難定位,這為以后的維護帶來了很多的麻煩. 

(3)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 
15 class MultiThread implements Runnable {
16     int a = 0;
17 
18     @Override
19     public void run() {
20         // 同步代碼塊,保證原子操作
21         synchronized ("") {
22             // 自增
23             a++;
24             try {
25                 // 線程休眠0.1秒
26                 Thread.sleep(100);
27             } catch (InterruptedException e) {
28                 e.printStackTrace();
29             }
30             // 自減
31             a--;
32             String tn = Thread.currentThread().getName();
33             System.out.println(tn + ":a =" + a);
34         }
35     }
36 }

 

 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-5:a =1
Thread-4:a =1
Thread-3:a =1
Thread-2:a =1
Thread-1:a =1

 

原本期望synchronized同步代碼塊中的邏輯都是原子邏輯,不受外界線程的干擾,但是結果卻出現原子邏輯被破壞的情況,這也是stop方法被廢棄的一個重要原因:破壞了原子邏輯.

既然終止一個線程不能用stop方法,那怎樣才能終止一個正在運行的線程呢?

使用自定義的標志位決定線程的執行情況,代碼如下:

 1 import java.util.Timer;
 2 import java.util.TimerTask;
 3 
 4 public class Client {
 5     public static void main(String[] args) throws InterruptedException {
 6         final SafeStopThread sst = new SafeStopThread();
 7         sst.start();
 8         //0.5秒后線程停止執行
 9         new Timer(true).schedule(new TimerTask() {
10             public void run() {
11                 sst.terminate();
12             }
13         }, 500);
14     }
15 
16 }
17 
18 class SafeStopThread extends Thread {
19     //此變量必須加上volatile
20     private volatile boolean stop = false;
21     @Override
22     public void run() {
23         //判斷線程體是否運行
24         while (stop) {
25             // Do Something
26             System.out.println("Stop");
27         }
28     }
29     //線程終止
30     public void terminate() {
31         stop = true;
32     }
33 }

 

在線程主題中判斷是否需要停止運行,即可保證線程體的邏輯完整性而且也不會破壞原值邏輯.

Thread還提供了一個interrupt中斷線程的方法,這個不是過時的方法,是否可以使用這個中斷線程?

很明確的說,interrupt不能終止一個正在執行着的線程,它只是修改中斷標志位而已.例如:

 1 public class Client {
 2     public static void main(String[] args) {
 3         Thread t1 = new Thread() {
 4             public void run() {
 5                 //線程一直運行
 6                 while (true) {
 7                     System.out.println("Running……");
 8                 }
 9             }
10         };
11         // 啟動t1線程
12         t1.start();
13         System.out.println(t1.isInterrupted());//false
14         // 中斷t1線程
15         t1.interrupt();
16         System.out.println(t1.isInterrupted());//true
17     }
18 }

 

執行這段代碼,會一直有Running在輸出,永遠不會停止,執行了interrupt沒有任何的變化,那是因為interrupt方法不能終止一個線程狀態,它只會改變中斷標志位.

在t1.interrupt()前后加上了t1.isInterrupted()會發現分別輸出的是false和true.

如果需要終止該線程,還需要執行進行判斷,例如我們可以使用interrupt編寫出更加簡潔,安全的終止線程的代碼:

 1 import java.util.Timer;
 2 import java.util.TimerTask;
 3 
 4 public class Client {
 5     public static void main(String[] args) throws InterruptedException {
 6         final SafeStopThread sst = new SafeStopThread();
 7         sst.start();
 8         //0.5秒后線程停止執行
 9         new Timer(true).schedule(new TimerTask() {
10             public void run() {
11                 sst.interrupt();
12             }
13         }, 500);
14     }
15 
16 }
17 
18 class SafeStopThread extends Thread {
19     @Override
20     public void run() {
21         //判斷線程體是否運行
22         while (!isInterrupted()) {
23             // Do Something
24         }
25     }    
26 }

 

總之,如果期望終止一個正在運行的線程,則不能使用已經過時的stop方法,需要執行編碼實現.這樣保證原子邏輯不被破壞,代碼邏輯不會出現異常.

當然還可以使用線程池,比如ThreadPoolExecutor類,那么可以通過shutdown方法逐步關閉線程池中的線程,它采用的是比較溫和,安全的關閉線程方法,完全不會產生類似stop方法的弊端.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM