您好,登錄后才能下訂單哦!
本篇內容介紹了“如何使用interrupt停止線程”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
啟動一個線程很簡單,調用Thread類的start()方法,并在run()方法中定義需要執行的任務就可以了,但是要正確的停止停止一個線程就需要注意了
通常情況下,我們不會手動停止一個線程,而是允許線程運行到結束,然后讓它自然停止。但是依然會有許多特殊的情況需要我們提前停止線程,比如:用戶突然關閉程序,或程序運行出錯重啟等。尤其是即將停止的線程在業務場景下仍然很有價值,我們就更需要正確的停止線程了。但是Java中并沒有直接提供能夠簡單安全的停止線程的能力。
Java 希望程序間能夠相互通知、相互協作地管理線程,因為如果不了解對方正在做的工作,貿然強制停止線程就可能會造成一些安全的問題,為了避免造成問題就需要給對方一定的時間來整理收尾工作。
比如:線程正在寫入一個文件,這時收到終止信號,它就需要根據自身業務判斷,是選擇立即停止,還是將整個文件寫入成功后停止,而如果選擇立即停止就可能造成數據不完整,不管是中斷命令發起者,還是接收者都不希望數據出現問題。
因此對于Java而言最正確的停止線程的方式是使用 interrupt。因為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 正確停止線程的情況。
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 沒有進行任何處理邏輯,相當于把中斷信號給隱藏了,這樣做是非常不合理的,所以需要正確處理異常,兩種最佳處理異常的方法如下:
void subTask2() throws InterruptedException { Thread.sleep(1000); }
每一個方法的調用方有義務去處理異常。調用方要不使用 try/catch 并在 catch 中正確處理異常,要不將異常聲明到方法簽名中。如果每層邏輯都遵守規范,便可以將中斷信號層層傳遞到頂層,最終讓 run() 方法可以捕獲到異常。而對于 run() 方法而言,它本身沒有拋出 checkedException 的能力,只能通過 try/catch 來處理異常。層層傳遞異常的邏輯保障了異常不會被遺漏,而對 run() 方法而言,就可以根據不同的業務邏輯來進行相應的處理。
private void reInterrupt() { try { Thread.sleep(2000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); e.printStackTrace(); } }
在 catch 語句中再次中斷線程。如代碼所示,需要在 catch 語句塊中調用 Thread.currentThread().interrupt() 函數。因為如果線程在休眠期間被中斷,那么會自動清除中斷信號。如果這時手動添加中斷信號,中斷信號依然可以被捕捉到。這樣后續執行的方法依然可以檢測到這里發生過中斷,可以做出相應的處理,整個線程可以正常退出。
幾種停止線程的錯誤方法。比如 stop(),suspend() 和 resume(),這些方法已經被 Java 直接標記為 @Deprecated。我們不應該再使用它們了。
stop():會直接把線程停止,這樣就沒有給線程足夠的時間來處理想要在停止前保存數據的邏輯,任務戛然而止,會導致出現數據完整性等問題。
suspend() 和 resume() :它們的問題在于如果線程調用 suspend(),它并不會釋放鎖,就開始進入休眠,但此時有可能仍持有鎖,這樣就容易導致死鎖問題,因為這把鎖在線程被 resume() 之前,是不會被釋放的。
例如:設線程 A 調用了 suspend() 方法讓線程 B 掛起,線程 B 進入休眠,而線程 B 又剛好持有一把鎖,此時假設線程 A 想訪問線程 B 持有的鎖,但由于線程 B 并沒有釋放鎖就進入休眠了,所以對于線程 A 而言,此時拿不到鎖,也會陷入阻塞,那么線程 A 和線程 B 就都無法繼續向下執行。
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 修飾的標記位可以正常工作的情況,但是如果我們說某個方法是正確的,那么它應該不僅僅是在一種情況下適用,而在其他情況下也應該是適用的。
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停止線程”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。