1. 程式人生 > >如何優雅的停止一個執行緒?

如何優雅的停止一個執行緒?

![](https://img2020.cnblogs.com/other/2024393/202010/2024393-20201012190414882-1780446894.png) 在之前的文章中 [i-code.online -《併發程式設計-執行緒基礎》](https://i-code.online/2020/10/07/2020-10-07-java-bing-fa-bian-cheng-ji-chu/#1-2-%E5%AE%9E%E7%8E%B0-Runnable-%E6%8E%A5%E5%8F%A3%E5%88%9B%E5%BB%BA%E7%BA%BF%E7%A8%8B)我們介紹了執行緒的建立和終止,從原始碼的角度去理解了其中的細節,那麼現在如果面試有人問你 “如何優雅的停止一個執行緒?”, 你該如何去回答尼 ?能不能完美的回答尼?
- 對於執行緒的停止,通常情況下我們是不會去手動去停止的,而是等待執行緒自然執行至結束停止,但是在我們實際開發中,會有很多情況中我們是需要提前去手動來停止執行緒,比如程式中出現異常錯誤,比如使用者關閉程式等情況中。在這些場景下如果不能很好地停止執行緒那麼就會導致各種問題,所以正確的停止程式是非常的重要的。 ## 強行停止執行緒會怎樣?
- 在我們平時的開發中我們很多時候都不會注意執行緒是否是健壯的,是否能優雅的停止,很多情況下都是貿然的強制停止正在執行的執行緒,這樣可能會造成一些安全問題,為了避免造成這種損失,我們應該給與執行緒適當的時間來處理完當前執行緒的收尾工作, 而不至於影響我們的業務。 - 對於 Java 而言,最正確的停止執行緒的方式是使用 `interrupt`。但 `interrupt `僅僅起到通知被停止執行緒的作用。而對於被停止的執行緒而言,它擁有完全的自主權,它既可以選擇立即停止,也可以選擇一段時間後停止,也可以選擇壓根不停止。可能很多同學會疑惑,既然這樣那這個存在的意義有什麼尼,其實對於 `Java` 而言,期望程式之間是能夠相互通知、協作的管理執行緒 - 比如我們有執行緒在進行 `io` 操作時,當程式正在進行寫檔案奧做,這時候接收到終止執行緒的訊號,那麼它不會立馬停止,它會根據自身業務來判斷該如何處理,是將整個檔案寫入成功後在停止還是不停止等都取決於被通知執行緒的處理。如果這裡立馬終止執行緒就可能造成資料的不完整性,這是我們業務所不希望的結果。 ##  interrupt 停止執行緒 - 關於 `interrupt` 的使用我們不在這裡過多闡述,可以看 [i-code.online -《併發程式設計-執行緒基礎》](https://i-code.online/2020/10/07/2020-10-07-java-bing-fa-bian-cheng-ji-chu/#1-2-%E5%AE%9E%E7%8E%B0-Runnable-%E6%8E%A5%E5%8F%A3%E5%88%9B%E5%BB%BA%E7%BA%BF%E7%A8%8B)文中的介紹,其核心就是通過呼叫執行緒的 `isInterrupt()` 方法進而判斷中斷訊號,當執行緒檢測到為 `true` 時則說明接收到終止訊號,此時我們需要做相應的處理
- 我們編寫一個簡單例子來看 ```java Thread thread = new Thread(() -> { while (true) { //判斷當前執行緒是否中斷, if (Thread.currentThread().isInterrupted()) { System.out.println("執行緒1 接收到中斷資訊,中斷執行緒...中斷標記:" + Thread.currentThread().isInterrupted()); //跳出迴圈,結束執行緒 break; } System.out.println(Thread.currentThread().getName() + "執行緒正在執行..."); } }, "interrupt-1"); //啟動執行緒 1 thread.start(); //建立 interrupt-2 執行緒 new Thread(() -> { int i = 0; while (i <20){ System.out.println(Thread.currentThread().getName()+"執行緒正在執行..."); if (i == 8){ System.out.println("設定執行緒中斷...." ); //通知執行緒1 設定中斷通知 thread.interrupt(); } i ++; try { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } },"interrupt-2").start(); ``` 上述程式碼相對比較簡單,我們建立了兩個執行緒,第一個執行緒我們其中做了中斷訊號檢測,當接收到中斷請求則結束迴圈,自然的終止執行緒,線上程二中,我們模擬當執行到 `i==8` 時通知執行緒一終止,這種情況下我們可以看到程式自然的進行的終止。
> 這裡有個思考: 當處於 `sleep` 時,執行緒能否感受到中斷訊號? - 對於這一特殊情況,我們可以將上述程式碼稍微修改即可進行驗證,我們將執行緒1的程式碼中加入 `sleep` 同時讓睡眠時間加長,讓正好執行緒2通知時執行緒1還處於睡眠狀態,此時觀察是否能感受到中斷訊號 ```java //建立 interrupt-1 執行緒 Thread thread = new Thread(() -> { while (true) { //判斷當前執行緒是否中斷, if (Thread.currentThread().isInterrupted()) { System.out.println("執行緒1 接收到中斷資訊,中斷執行緒...中斷標記:" + Thread.currentThread().isInterrupted()); Thread.interrupted(); // //對執行緒進行復位,由 true 變成 false System.out.println("經過 Thread.interrupted() 復位後,中斷標記:" + Thread.currentThread().isInterrupted()); //再次判斷是否中斷,如果是則退出執行緒 if (Thread.currentThread().isInterrupted()) { break; } break; } System.out.println(Thread.currentThread().getName() + "執行緒正在執行..."); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } }, "interrupt-1"); ``` 我們執行修改後的程式碼,發現如果 `sleep`、`wait `等可以讓執行緒進入阻塞的方法使執行緒休眠了,而處於休眠中的執行緒被中斷,那麼執行緒是可以感受到中斷訊號的,並且會丟擲一個 `InterruptedException `異常,同時清除中斷訊號,將中斷標記位設定成 `false`。這樣一來就不用擔心長時間休眠中執行緒感受不到中斷了,因為即便執行緒還在休眠,仍然能夠響應中斷通知,並丟擲異常。
> 對於執行緒的停止,最優雅的方式就是通過 `interrupt` 的方式來實現,關於他的詳細文章看之前文章即可,如 `InterruptedException` 時,再次中斷設定,讓程式能後續繼續進行終止操作。不過對於 `interrupt` 實現執行緒的終止在實際開發中發現使用的並不是很多,很多都可能喜歡另一種方式,通過標記位。 ## 用 volatile 標記位的停止方法 - 關於 `volatile` 作為標記位的核心就是他的可見性特性,我們通過一個簡單程式碼來看: ```java /** * @ulr: i-code.online * @author: zhoucx * @time: 2020/9/25 14:45 */ public class MarkThreadTest { //定義標記為 使用 volatile 修飾 private static volatile boolean mark = false; @Test public void markTest(){ new Thread(() -> { //判斷標記位來確定是否繼續進行 while (!mark){ try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("執行緒執行內容中..."); } }).start(); System.out.println("這是主執行緒走起..."); try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } //10秒後將標記為設定 true 對執行緒可見。用volatile 修飾 mark = true; System.out.println("標記位修改為:"+mark); } } ``` 上面程式碼也是我們之前文中的,這裡不再闡述,就是一個設定標記,讓執行緒可見進而終止程式,這裡我們需要討論的是,使用 `volatile` 是真的都是沒問題的,上述場景是沒問題,但是在一些特殊場景使用 `volatile` 時是存在問題的,這也是需要注意的!
### volatile 修飾標記位不適用的場景 - 這裡我們使用一個生產/消費的模式來實現一個 `Demo` ```java /** * @url: i-code.online * @author: zhoucx * @time: 2020/10/12 10:46 */ public class Producter implements Runnable { //標記是否需要產生數字 public static volatile boolean mark = true; Block