協作,才能更好的中斷執行緒
- 2019 年 10 月 9 日
- 筆記
聊起中斷,大家可能最熟悉的例子就是執行緒休眠。下面就是一個執行緒休眠的 demo,在這個例子中,當我們調用 sleep
方法,該方法將會拋出一個需要捕獲的中斷異常,這裡捕獲該異常並直接返回。
for (int i = 0; i < somethings.size(); i++) { // 休眠 4 s try { Thread.sleep(4000); } catch (InterruptedException e) { // 拋出中斷異常 return; } // 輸出 System.out.println(somethings.get(i)); }
除了 InterruptedException
中斷異常,另外還有三個中斷相關的方法,三個方法都與執行緒相關。
- thread#interrupt
- Thread#interrupted
- thread#isInterrupted
interrupt
方法用於中斷執行緒,但是並不是說該方法就能直接使執行緒停止。
下面使用 interrupt
中斷執行緒,這裡我們期望中斷直接停止子執行緒輸出。但是當主執行緒調用子執行緒 interrupt
方法,子執行緒並卻沒有被終止,還在繼續列印數字。
Runnable interruptedTask=new Runnable() { @Override public void run() { for (int i = 0; i <Integer.MAX_VALUE ; i++) { System.out.println(i); } } }; Thread interruptThread=new Thread(interruptedTask); interruptThread.start(); // 休眠 5 s, TimeUnit.SECONDS.sleep(2); // 中斷當前執行緒 interruptThread.interrupt(); // 再次休眠,觀察子執行緒 TimeUnit.SECONDS.sleep(2);
引用 Java 官方對於中斷的解釋:
An interrupt is an indication to a thread that it should stop what it is doing and do something else. It’s up to the programmer to decide exactly how a thread responds to an interrupt, but it is very common for the thread to terminate
中斷僅僅只是表明這個執行緒可以停止,但是執行緒是否停止完全取決於執行緒自己。只有執行緒相互協作,才能更好的停止執行緒。
每個執行緒都包含一個內部標誌,用來表示中斷狀態。調用執行緒的 interrupt
方法將會設置該狀態位,對於 Thread#sleep
等阻塞方法,將會拋出 InterruptedException
,並清除中斷標誌。
我們可以使用 thread#isInterrupted
或 Thread#interrupted
檢查中斷狀態。但是需要注意,兩個方法存在一些區別,Thread#interrupted
為靜態類方法,該方法檢測到中斷之後就會清除中斷標誌。
上面的方法我們只要加上中斷狀態判斷就也可以停止執行緒。
Runnable interruptedTask=new Runnable() { @Override public void run() { for (int i = 0; i <Integer.MAX_VALUE ; i++) { // 一旦檢測到中斷標誌,停止執行緒 if(Thread.interrupted()){ System.out.println("interrupted!!!!"); break; } System.out.println(i); } } }; Thread interruptThread=new Thread(interruptedTask); interruptThread.start(); // 休眠 5 s, TimeUnit.SECONDS.sleep(2); // 中斷當前執行緒 interruptThread.interrupt(); // 再次休眠,觀察子執行緒 TimeUnit.SECONDS.sleep(2); }
中斷最佳實踐
不要隨意『吃掉』中斷異常
由於中斷異常是一個 checked exception,我們不得不需要處理該異常。如果我們可以保證該異常不影響應用,我們可以直接『吃掉』這個異常。其他情況下我們需要正確處理這個異常。
最簡單的做法就是不處理該異常,直接向上拋出中斷異常,讓上層調用者決定如何處理。
但是有些情況下,卻不適合上面的做法,這種情況下我們需要在 catch 中處理中斷。如果實在不知道如何處理,那就是記錄該異常,並使用日誌方式輸出。
中斷不會停止阻塞 IO
上面我們說到,對於一些阻塞方法如 Thread#sleep
,將會拋出中斷異常。但是對於 Socket 等阻塞 IO 調用,並不會拋出這個異常。也就是說中斷並不會停止阻塞 IO 的調用。
這是因為當調用 Thread#sleep
等阻塞方法時,Java 執行緒狀態將會從 RUNNABLE
轉變為 TIMED_WAITING
或 WATTING
。而當執行緒阻塞在 IO 讀取時,Java 執行緒實際狀態卻還是 RUNNABLE
。如果你對這個執行緒狀態還有疑惑,可以閱讀下這篇文章 面試官:都說阻塞 I/O 模型將會使執行緒休眠,為什麼 Java 執行緒狀態卻是 RUNNABLE?,深入理解一下執行緒狀態。
本文首發於studyidea.cn
歡迎關注我的公眾號:程式通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的部落格:studyidea.cn