中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何使用interrupt停止線程

發布時間:2021-12-31 09:20:13 來源:億速云 閱讀:158 作者:iii 欄目:大數據

本篇內容介紹了“如何使用interrupt停止線程”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

啟動一個線程很簡單,調用Thread類的start()方法,并在run()方法中定義需要執行的任務就可以了,但是要正確的停止停止一個線程就需要注意了

一:如何正確的停止線程

通常情況下,我們不會手動停止一個線程,而是允許線程運行到結束,然后讓它自然停止。但是依然會有許多特殊的情況需要我們提前停止線程,比如:用戶突然關閉程序,或程序運行出錯重啟等。尤其是即將停止的線程在業務場景下仍然很有價值,我們就更需要正確的停止線程了。但是Java中并沒有直接提供能夠簡單安全的停止線程的能力。

1.1 線程停止設計原則

Java 希望程序間能夠相互通知、相互協作地管理線程,因為如果不了解對方正在做的工作,貿然強制停止線程就可能會造成一些安全的問題,為了避免造成問題就需要給對方一定的時間來整理收尾工作。

比如:線程正在寫入一個文件,這時收到終止信號,它就需要根據自身業務判斷,是選擇立即停止,還是將整個文件寫入成功后停止,而如果選擇立即停止就可能造成數據不完整,不管是中斷命令發起者,還是接收者都不希望數據出現問題。

因此對于Java而言最正確的停止線程的方式是使用 interrupt。因為interrupt 僅僅起到通知被停止線程的作用。而對于被停止的線程而言,它擁有完全的自主權,它既可以選擇立即停止,也可以選擇一段時間后停止,也可以選擇壓根不停止。

1.2 如何使用interrupt停止線程

while (!Thread.currentThread().isInterrupted() && has more work to do) {
    do more work
}

調用用某個線程的 interrupt() 方法之后,這個線程的中斷標記位就會被設置成 true。每個線程都有這樣的標記位,當線程執行時,應該定期檢查這個標記位,如果標記位被設置成 true,就說明有程序想終止該線程。在上面偽代碼中可以看到,在 while 循環體判斷語句中,首先通過 Thread.currentThread().isInterrupt() 判斷線程是否被中斷,隨后檢查是否還有工作要做。

使用interrupt正確的停止線程:

public class StopThread implements Runnable{
    @Override
    public void run() {
        int count = 0;
        // 退出循環表示中斷標志位被設置為了true(有人想停止線程)或任務完成
        while(!Thread.currentThread().isInterrupted() && count < 1000){
            // 中斷標記為false(未改變)、且還有任務做則進入循環,每次都判斷一下
            System.out.println("count = " + count++);
        }
    }

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(new StopThread());
        thread.start();
        // 執行當前語句的線程進行睡眠
        Thread.sleep(5);
        thread.interrupt();
    }
}

在 main 函數中會啟動該子線程,然后主線程休眠 5 毫秒后立刻給該子線程發送中斷信號,該子線程會檢測到中斷信號,于是在還沒打印完1000個數的時候就會停下來,這種就屬于通過 interrupt 正確停止線程的情況。

1.2.1 線程在Sleep期間能否感受到interrupt中斷

Java 設計者在設計之初就考慮到了這一點。sleep、wait 等可以讓線程進入阻塞的方法使線程休眠了,而處于休眠中的線程被中斷,那么線程是可以感受到中斷信號的,并且會拋出一個 InterruptedException 異常,同時清除中斷信號,將中斷標記位設置成 false。這樣一來就不用擔心長時間休眠中線程感受不到中斷了,因為即便線程還在休眠,仍然能夠響應中斷通知,并拋出異常。

public class StopDuringSleep implements Runnable{

    @Override
    public void run() {
        int num = 0;
        while(!Thread.currentThread().isInterrupted() && num <= 1000){
            try {
                System.out.println("num = "+num++);
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

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

上面代碼的邏輯為:主線程休眠 5 毫秒后,通知子線程中斷,此時子線程仍在執行 sleep 語句,處于休眠中。執行代碼結果發現程序仍在繼續執行,但拋出了InterruptedException異常信息。可以看出線程即使在在Sleep期間也能夠感受到interrupt中斷信息,程序仍在運行沒有正確停止的原因是我們沒有正確處理這個異常。

在實際開發中,如果我們負責編寫的方法需要被別人調用,同時我們的方法內調用了 sleep 或者 wait 等能響應中斷的方法時,僅僅 catch 住異常是不夠的。如上面的代碼所示,catch 語句塊里代碼是空的,它并沒有進行任何處理。假設線程執行到這個方法,并且正在 sleep,此時有線程發送 interrupt 通知試圖中斷線程,就會立即拋出異常,并清除中斷信號。拋出的異常被 catch 語句塊捕捉。但是,捕捉到異常的 catch 沒有進行任何處理邏輯,相當于把中斷信號給隱藏了,這樣做是非常不合理的,所以需要正確處理異常,兩種最佳處理異常的方法如下:

1)方法簽名拋異常,run() 強制 try/catch
void subTask2() throws InterruptedException {
    Thread.sleep(1000);
}

每一個方法的調用方有義務去處理異常。調用方要不使用 try/catch 并在 catch 中正確處理異常,要不將異常聲明到方法簽名中。如果每層邏輯都遵守規范,便可以將中斷信號層層傳遞到頂層,最終讓 run() 方法可以捕獲到異常。而對于 run() 方法而言,它本身沒有拋出 checkedException 的能力,只能通過 try/catch 來處理異常。層層傳遞異常的邏輯保障了異常不會被遺漏,而對 run() 方法而言,就可以根據不同的業務邏輯來進行相應的處理。

2)再次中斷
private void reInterrupt() {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        e.printStackTrace();
    }
}

在 catch 語句中再次中斷線程。如代碼所示,需要在 catch 語句塊中調用 Thread.currentThread().interrupt() 函數。因為如果線程在休眠期間被中斷,那么會自動清除中斷信號。如果這時手動添加中斷信號,中斷信號依然可以被捕捉到。這樣后續執行的方法依然可以檢測到這里發生過中斷,可以做出相應的處理,整個線程可以正常退出。

二:錯誤的停止線程的方法

2.1 常見的錯誤停止線程的方法

幾種停止線程的錯誤方法。比如 stop(),suspend() 和 resume(),這些方法已經被 Java 直接標記為 @Deprecated。我們不應該再使用它們了。

  • stop():會直接把線程停止,這樣就沒有給線程足夠的時間來處理想要在停止前保存數據的邏輯,任務戛然而止,會導致出現數據完整性等問題。

  • suspend() 和 resume() :它們的問題在于如果線程調用 suspend(),它并不會釋放鎖,就開始進入休眠,但此時有可能仍持有鎖,這樣就容易導致死鎖問題,因為這把鎖在線程被 resume() 之前,是不會被釋放的。

例如:設線程 A 調用了 suspend() 方法讓線程 B 掛起,線程 B 進入休眠,而線程 B 又剛好持有一把鎖,此時假設線程 A 想訪問線程 B 持有的鎖,但由于線程 B 并沒有釋放鎖就進入休眠了,所以對于線程 A 而言,此時拿不到鎖,也會陷入阻塞,那么線程 A 和線程 B 就都無法繼續向下執行。

2.2 為什么用 volatile 標記位的停止線程方法是錯誤的

volatile 修飾標記位適用的場景

public class VolatileCanStop implements Runnable{

    private volatile boolean canceled = false;

    @Override
    public void run() {
        int num = 0;
        try {
            while (!canceled && num <= 1000000) {
                if (num % 10 == 0) {
                    System.out.println(num + "是10的倍數。");
                }
                num++;
                Thread.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {

        VolatileCanStop volatileCanStop = new VolatileCanStop();
        Thread thread = new Thread(volatileCanStop);
        thread.start();
        Thread.sleep(300);
        volatileCanStop.canceled = true;
    }
}

run() 方法中進行 while 循環,在循環體中又進行了兩層判斷,首先判斷 canceled 變量的值,canceled 變量是一個被 volatile 修飾的初始值為 false 的布爾值,當該值變為 true 時,while 跳出循環,while 的第二個判斷條件是 num 值小于1000000(一百萬),在while 循環體里,只要是 10 的倍數就打印出來,然后 num++。

接下來,首先啟動線程,然后經過 3 秒鐘的時間,把用 volatile 修飾的布爾值的標記位設置成 true,這樣,正在運行的線程就會在下一次 while 循環中判斷出 canceled 的值已經變成 true 了,這樣就不再滿足 while 的判斷條件,跳出整個 while 循環,線程就停止了,這種情況是演示 volatile 修飾的標記位可以正常工作的情況,但是如果我們說某個方法是正確的,那么它應該不僅僅是在一種情況下適用,而在其他情況下也應該是適用的

volatile 修飾標記位不適用的場景

public class VolatileCannotStop {

   /**
    * 生產者
    */
   static class Producer implements Runnable{

       public volatile boolean canceled = false;
       BlockingQueue storage;

       public Producer(BlockingQueue storage) {
           this.storage = storage;
       }

       @Override
       public void run() {
           try{
               int num = 0;
               // 停止線程信號為true,且任務執行完畢
               while(num <= 100000 && !canceled){
                   if (num % 50 == 0) {
                       // 生產者隊列滿了之后,會使當前線程阻塞,阻塞后(被叫醒前)不能進入下一次循環,此時是感知不到canceled的狀態的
                       storage.put(num);
                       System.out.println(num + "是50的倍數,被放到倉庫中了。");
                   }
                   num++;
               }
           }catch (InterruptedException e){
               e.printStackTrace();
           }finally {
               System.out.println("生產者結束運行");
           }
       }
   }

   /**
    * 消費者
    */
   static class Consumer{

       BlockingQueue storage;

       public Consumer(BlockingQueue storage) {
           this.storage = storage;
       }
       public boolean needMoreNums() {
           if (Math.random() > 0.97) {
               return false;
           }
           return true;
       }
   }

   public static void main(String[] args) throws InterruptedException {

       ArrayBlockingQueue storage = new ArrayBlockingQueue(8);

       Producer producer = new Producer(storage);
       Thread producerThread = new Thread(producer);
       producerThread.start();
       Thread.sleep(500);

       Consumer consumer = new Consumer(storage);
       while (consumer.needMoreNums()) {
           System.out.println(consumer.storage.take() + "被消費了");
           Thread.sleep(100);
       }
       System.out.println("消費者不需要更多數據了。");

       //一旦消費不需要更多數據了,我們應該讓生產者也停下來,但是實際情況卻停不下來
       producer.canceled = true;
       System.out.println(producer.canceled);
   }
}

main 函數中,首先創建了生產者/消費者共用的倉庫 BlockingQueue storage,倉庫容量是 8,并且建立生產者并將生產者放入線程后啟動線程,啟動后進行 500 毫秒的休眠,休眠時間保障生產者有足夠的時間把倉庫塞滿,而倉庫達到容量后就不會再繼續往里塞,這時生產者會阻塞,500 毫秒后消費者也被創建出來,并判斷是否需要使用更多的數字,然后每次消費后休眠 100 毫秒。

當消費者不再需要數據,就會將 canceled 的標記位設置為 true,理論上此時生產者會跳出 while 循環,打印輸出canceled的值為true,并打印輸出“生產者運行結束”。

然而結果卻不是我們想象的那樣,盡管已經把 canceled 設置成 true,但生產者仍然沒有停止,這是因為在這種情況下,生產者在執行 storage.put(num) 時發生阻塞,在它被叫醒之前是沒有辦法進入下一次循環判斷 canceled 的值的,所以在這種情況下用 volatile 是沒有辦法讓生產者停下來的,相反如果用 interrupt 語句來中斷,即使生產者處于阻塞狀態,仍然能夠感受到中斷信號,并做響應處理

“如何使用interrupt停止線程”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

米脂县| 长治市| 辽中县| 岳池县| 元谋县| 普格县| 利津县| 德化县| 西青区| 普安县| 尤溪县| 云林县| 株洲市| 伽师县| 新余市| 乐都县| 黄梅县| 华蓥市| 高台县| 南宫市| 南丰县| 灵川县| 本溪| 新宾| 陕西省| 长武县| 宝兴县| 南丹县| 惠安县| 玉环县| 新营市| 金沙县| 农安县| 琼结县| 垦利县| 平定县| 海南省| 台北县| 利川市| 建水县| 长汀县|