如何正確停止線程


1、原理介紹:

使用interrupt來通知,而不是強制

在Java中,最好的停止線程的方式是使用中斷 Interrupt,但是這僅僅是會通知到被終止的線程“你該停止運行了”,被終止的線程自身擁有決定權(決定是否、以及何時停止),這依賴於請求停止方和被停止方都遵守一種約定好的編碼規范。

任務和線程的啟動很容易。在大多數時候,我們都會讓它們運行直到結東,或者讓它們自行停止。然而有時候我們希望提前結東任務或線程或許是因為用戶取消了操作,或者服務要被快速關閉,或者是運行超時或出錯了。要使任務和線程能安全、快速、可靠地停止下來,並不是一件容易的事。Java沒有提供任何機制來安全地終止線程。但它提供了中斷( Interruption這是一種協作機制,能夠使一個線程終止另一個線程的當前工作)。

這種協作式的方法是必要的,我們很少希望某個任務、線程或服務立即停止,因為這種立即停止會使共享的數據結構處於不一致的狀態。相反,在編寫任務和服務時可以使用一種協作的方式:當需要停止時,它們首先會清除當前正在執行的工作,然后再結束。這提供了更好的靈活性,因為任務本身的代碼比發出取消請求的代碼更清楚如何執行清除工作。

生命周期結束(End-of-Lifecycle)的問題會使任務、服務以及程序的設計和實現等過程變得復雜而這個在程序設計中非常重要的要素卻經常被忽略。一個在行為良好的軟件與勉強運的軟件之間的最主要區別就是,行為良好的軟件能很完善地處理失敗、關閉和取消等過程。


 

2、如何正確停止線程

  •   線程通常在什么情況停止普通情況。
    • 執行完所有代碼
    • 有異常未捕獲。

 


 

3、使用interrupt停止的幾種情況。

  • 最普遍的情況
/**
 * @data 2019/11/9 - 下午8:07
 * 描述:run方法內沒有sleep或wait方法時,停止線程
 */
public class RightWayStopThreadWithoutSleep implements Runnable{
    @Override
    public void run() {
        int num = 0;
        while(num<=Integer.MAX_VALUE/2 && !Thread.currentThread().isInterrupted()){
            if(num%10000 == 0)
                System.out.println(num+"是10000的倍數");
            num++;
        }
        System.out.println("任務結束");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
        thread.start();
        thread.sleep(1000);
        thread.interrupt();
    }
}
  • 當停止線程遇到線程阻塞
/**
 * @data 2019/11/9 - 下午8:07
 * 描述:帶有sleep的中斷線程的方法
 */
public class RightWayStopThreadWithSleep {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = ()->{
            int num = 0;
            try {
                while (num<=300 && !Thread.currentThread().isInterrupted()){
                    if(num%100 == 0)
                        System.out.println(num+"是100的倍數");

                    num++;
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(500);
        thread.interrupt();
    }
}
結果:
0是100的倍數 100是100的倍數 200是100的倍數 300是100的倍數 java.lang.InterruptedException: sleep interrupted at java.base
/java.lang.Thread.sleep(Native Method) at threadcoreknowledge.stopthreads.RightWayStopThreadWithSleep.lambda$main$0(RightWayStopThreadWithSleep.java:18) at java.base/java.lang.Thread.run(Thread.java:844)

sleep、wait等一些方法使線程阻塞,當線程收到通知interrupt時候,這些方法處理該通知的方法是拋出InterruptedException異常。

  • 如果線程每次迭代后都阻塞
/**
 * @data 2019/11/10 - 上午9:13
 * 描述:如果每次執行過程中,每次循環中都調用sleep或wait等方法時,那么不需要再每次迭代過程中檢查是否已中斷。
 */
public class RightWayStopTHreadWithSleepEveryLoop {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = ()->{
            int num = 0;
            try {
                while (num<=10000){
                    if(num%100 == 0)
                        System.out.println(num+"是100的倍數");
                    Thread.sleep(10);
                    num++;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };

        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

 

 在循環過程中,cpu運行速度快,大部分時間都熬在使其阻塞的方法中,所以沒必要每次迭代檢查是否已中斷-(Thread.currentThread().isInterrupted())


 

4、如果while里面放try/catch,會導致中斷失效

 
         
/**
* @data 2019/11/10 - 上午9:24
* 描述:如果while里面放try/catch,會導致中斷失效
*/
public class CantInterrupt {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = ()->{
int num = 0;
while(num<10000&& !Thread.currentThread().isInterrupted()){
if(num%100 == 0){
System.out.println(num+"是100的倍數");
}
num++;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
 

 

結果:

0是100的倍數
100是100的倍數
200是100的倍數
300是100的倍數
400是100的倍數
java.lang.InterruptedException: sleep interrupted
    at java.base/java.lang.Thread.sleep(Native Method)
    at threadcoreknowledge.stopthreads.CantInterrupt.lambda$main$0(CantInterrupt.java:17)
    at java.base/java.lang.Thread.run(Thread.java:844)
500是100的倍數
600是100的倍數
700是100的倍數

 

即使加了檢查是否已中斷,但程序拋出了異常后while里面的內容依然執行,這是因為當sleep、wait等函數阻塞線程后,會將該線程的阻塞標志清除,這就導致即使通知終端信號給線程了,線程檢測不出


 

5、實際開發中處理終端的最佳方法:

/**
 * @data 2019/11/10 - 上午9:41
 * 描述:最佳實踐:catch了InterruptedExcetion只有的優先選擇:在方法簽名中拋出異常
 * 那么在run()就會強制try/catch
 */
public class RightWayStopTHreadInProd implements Runnable{

    @Override
    public void run() {
        while (true){
            try {
                System.out.println("go");
                throwInMethod();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void throwInMethod() throws InterruptedException {
        Thread.sleep(2000);
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new RightWayStopTHreadInProd());
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

 

原因:

優先選擇在方法上拋出異常。

用 throws Interruptedexception標記你的方法,不采用ty語句塊捕獲異常,以便於該異常可以傳遞到頂層,讓run方法可以捕獲這一異常。

由於run方法內無法拋出 checked Exception(只能用 try catch),頂層方法必須處理該異常,遵免了漏掉或者被吞掉的情況,增強了代碼的健壯性。

 


免責聲明!

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



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