java 停止線程的正確方式

在看一個問題的時候突然想到之前面試,有一個面試官問我,怎麼停止線程,突然斷片了,趁着現在有時間,做一下總結。

在網上看了幾篇文章,我這篇文章大致記錄一下,

1、第一種方式:使用stop方法終止線程

這種方式最直接了當,但是也是不可取的,調用stop()方法時會拋出java.lang.ThreadDeath異常,但是通常情況下,此異常不需要顯示地捕捉

public class MyThread extends Thread {
    private int i = 0;
    public void run(){
        super.run();
        try {
            this.stop();
        } catch (ThreadDeath e) {
            System.out.println("進入異常catch");
            e.printStackTrace();
        }
    }
}

public class Run {
    public static void main(String args[]) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();
    }
}

程序中可以直接使用thread.stop()來強行終止線程,但是stop方法是很危險的,就象突然關閉計算機電源,而不是按正常程序關機一樣,可能會產生不可預料的結果,不安全主要是:thread.stop()調用之後,創建子線程的線程就會拋出ThreadDeatherror的錯誤,並且會釋放子線程所持有的所有鎖。一般任何進行加鎖的代碼塊,都是爲了保護數據的一致性,如果在調用thread.stop()後導致了該線程所持有的所有鎖的突然釋放(不可控制),那麼被保護數據就有可能呈現不一致性,其他線程在使用這些被破壞的數據時,有可能導致一些很奇怪的應用程序錯誤。因此,並不推薦使用stop方法來終止線程。

2、第二種方式 設置標識位:

就是繼承線程 添加自己的成員變量,當變量狀態改變時,停止線程:

public class ThreadTest extends Thread {
///使用volatile目的是保證可見性,一處修改了標誌,處處都要去主存讀取新的值,而不是使用緩存  
    public volatile boolean exit = false;

    public void run() {
        while (!exit) {
            for (int i = 1; i < 100; i++) {
                System.out.print(">>>>:i:" + i + "\n");
            }
        }
    }
}

3、第三種方式:使用interrupt()中斷的方式

3.1 interrupt()方法的使用效果並不像for+break語句那樣,馬上就停止循環。調用interrupt方法是在當前線程中打了一個停止標誌,並不是真的停止線程。

public class MyThread extends Thread {
    public void run(){
        super.run();
        for(int i=0; i<500000; i++){
            System.out.println("i="+(i+1));
        }
    }
}

public class Run {
    public static void main(String args[]){
        Thread thread = new MyThread();
        thread.start();
        try {
            Thread.sleep(2000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

輸出結果:

...
i=499994
i=499995
i=499996
i=499997
i=499998
i=499999
i=500000

3.2 判斷線程是否停止狀態

Thread.java類中提供了兩種方法:

  • this.interrupted(): 測試當前線程是否已經中斷;

內部實現是調用的當前線程的isInterrupted(),並且會重置當前線程的中斷狀態。由該方法清除如果線程中斷,第一次返回true 後修改中斷狀態,再次調用返回false,如果連續兩次調用該方法,則第二次調用返回false。

  • this.isInterrupted(): 測試線程是否已經中斷;

isInterrupted()是實例方法,是調用該方法的對象所表示的那個線程的isInterrupted(),不會重置當前線程的中斷狀態。

3.3 使用interrupt()方法來中斷線程有兩種情況

1> 線程阻塞狀態

如使用了sleep,同步鎖的wait,socket中的receiver,accept等方法時,會使線程處於阻塞狀態。

當調用線程的interrupt()方法時,會拋出InterruptException異常。阻塞中的那個方法拋出這個異常,通過代碼捕獲該異常,然後break跳出循環狀態,從而讓我們有機會結束這個線程的執行。通常很多人認爲只要調用interrupt方法線程就會結束,實際上是錯的, 一定要先捕獲InterruptedException異常之後通過break來跳出循環,才能正常結束run方法。

public class ThreadTest extends Thread {
    public void run() { 
        while (true){
            try{
                    Thread.sleep(5*1000);//阻塞5妙
                }catch(InterruptedException e){
                    e.printStackTrace();
                    break;//捕獲到異常之後,執行break跳出循環。
                }
        }
    } 
}

2>線程未處於阻塞狀態

使用isInterrupted()判斷線程的中斷標誌來退出循環。當使用interrupt()方法時,中斷標誌就會置true,和使用自定義的標誌來控制循環是一樣的道理。 

public class ThreadTest extends Thread {
    public void run() { 
        while (!isInterrupted()){
            //do something, but no throw InterruptedException
        }
    } 
}

爲什麼要區分進入阻塞狀態和和非阻塞狀態兩種情況了,是因爲當阻塞狀態時,如果有interrupt()發生,系統除了會拋出InterruptedException異常外,還會調用interrupted()函數,調用時能獲取到中斷狀態是true的狀態,調用完之後會復位中斷狀態爲false,所以異常拋出之後通過isInterrupted()是獲取不到中斷狀態是true的狀態,從而不能退出循環,因此在線程未進入阻塞的代碼段時是可以通過isInterrupted()來判斷中斷是否發生來控制循環,在進入阻塞狀態後要通過捕獲異常來退出循環。因此使用interrupt()來退出線程的最好的方式應該是兩種情況都要考慮:

public class ThreadTest extends Thread {
    public void run() { 
        while (!isInterrupted()){ //非阻塞過程中通過判斷中斷標誌來退出
            try{
                Thread.sleep(5*1000);//阻塞過程捕獲中斷異常來退出
            }catch(InterruptedException e){
                e.printStackTrace();
                break;//捕獲到異常之後,執行break跳出循環。
            }
        }
    } 
}

總結

對於線程的終止,個人覺得用得最多也最安全的還是屬於第二種使用volatile標誌位來終止線程,因爲第三種的使用受到很多方面的制約,一旦沒用好會出各種問題。

得看你的具體業務場景,如果是標誌位中斷的話,標誌位的位置安放也是要注意的,如果你用到的是第三種interrupt中斷線程就得綜合考慮線程所處的狀態,如果拋異常那麼這個異常拋出來的之後的代碼處理也是正確中斷線程的關鍵。
 

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