線程不是你想中斷就能中斷

2021-01-12 51CTO

本文轉載自微信公眾號「JerryCodes」,作者KyleJerry。轉載本文請聯繫JerryCodes公眾號。

為什麼不強制停止 如何用 interrupt 停止線程 sleep 期間能否感受到中斷 停止線程的方式有幾種 總結

啟動線程需要調用 Thread 類的 start() 方法,並在 run() 方法中定義需要執行的任務。啟動一個線程非常簡單,但如果想要正確停止它就沒那麼容易了。

對於實現線程的幾種方式,可見我的上一篇文章

實現線程本質上只有一種方式

為什麼不強制停止

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

為什麼 Java 不提供強制停止線程的能力呢?事實上,Java 希望程序間能夠相互通知、相互協作地管理線程,因為如果不了解對方正在做的工作,貿然強制停止線程就可能會造成一些安全的問題。

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

如何用 interrupt 停止線程

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

我們一旦調用某個線程的 interrupt() 之後,這個線程的中斷標記位就會被設置成true。每個線程都有這樣的標記位,當線程執行時,應該定期檢查這個標記位,如果標記位被設置成 true,就說明有程序想終止該線程。

回到源碼,可以看到在 while 循環體判斷語句中,首先通過

Thread.currentThread().isInterrupt()

判斷線程是否被中斷,隨後檢查是否還有工作要做。&& 邏輯表示只有當兩個判斷條件同時滿足的情況下,才會去執行下面的工作。

public class StopThread implements Runnable {       @Override     public void run() {         int count = 0;         while (!Thread.currentThread().isInterrupted() && count < 1000) {             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();     } } 

在 StopThread 類的 run() 方法中,首先判斷線程是否被中斷,然後判斷 count 值是否小於 1000。

這個線程的工作內容很簡單,就是列印 0~999 的數字,每列印一個數字 count 值加 1,可以看到,線程會在每次循環開始之前,檢查是否被中斷了。接下來在 main 函數中會啟動該線程,然後休眠 5 毫秒後立刻中斷線程,該線程會檢測到中斷信號,於是在還沒列印完1000個數的時候就會停下來,這種就屬於通過 interrupt 正確停止線程的情況。

sleep 期間能否感受到中斷

先說結論,可以。

public class StopDuringSleep {       public static void main(String[] args) throws InterruptedException {         Runnable runnable = () -> {             int num = 0;             try {                 while (!Thread.currentThread().isInterrupted() && num <= 1000) {                     System.out.println(num);                     num++;                     Thread.sleep(1000000);                 }             } catch (InterruptedException e) {                 e.printStackTrace();             }         };         Thread thread = new Thread(runnable);         thread.start();         Thread.sleep(5);         thread.interrupt();     } } 

運行後的結果你猜怎麼著,程序會拋出異常

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

但是這樣只能相應一次中斷信號了,怎麼辦?我的業務還沒有完成收尾,怎麼辦?

合理利用好 try/catch

我們在實際開發中不能盲目吞掉中斷,如果不在方法籤名中聲明,也不在 catch 語句塊中再次恢復中斷,而是在 catch 中不作處理,我們稱這種行為是「屏蔽了中斷請求」。如果我們盲目地屏蔽了中斷請求,會導致中斷信號被完全忽略,最終導致線程無法正確停止。

try {         Thread.sleep(2000);     } catch (InterruptedException e) { //        此處處理中斷異常請求,業務收尾     } 

停止線程的方式有幾種

void shutdown; boolean isShutdown; boolean isTerminated; boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; List<Runnable> shutdownNow; 

下面我們就對這些方法逐一展開。

shutdown()

調用 shutdown() 方法之後線程池並不是立刻就被關閉,因為這時線程池中可能還有很多任務正在被執行,或是任務隊列中有大量正在等待被執行的任務,調用 shutdown() 方法後線程池會在執行完正在執行的任務和隊列中等待的任務後才徹底關閉。但這並不代表 shutdown() 操作是沒有任何效果的,調用 shutdown() 方法後如果還有新的任務被提交,線程池則會根據拒絕策略直接拒絕後續新提交的任務。

isShutdown()

它可以返回 true 或者 false 來判斷線程池是否已經開始了關閉工作,也就是是否執行了 shutdown 或者 shutdownNow 方法。這裡需要注意,如果調用 isShutdown() 方法的返回的結果為 true 並不代表線程池此時已經徹底關閉了,這僅僅代表線程池開始了關閉的流程,也就是說,此時可能線程池中依然有線程在執行任務,隊列裡也可能有等待被執行的任務。

isTerminated()

這個方法可以檢測線程池是否真正「終結」了,這不僅代表線程池已關閉,同時代表線程池中的所有任務都已經都執行完畢了,因為我們剛才說過,調用 shutdown 方法之後,線程池會繼續執行裡面未完成的任務,不僅包括線程正在執行的任務,還包括正在任務隊列中等待的任務。比如此時已經調用了 shutdown 方法,但是有一個線程依然在執行任務,那麼此時調用 isShutdown 方法返回的是 true ,而調用 isTerminated 方法返回的便是 false ,因為線程池中還有任務正在在被執行,線程池並沒有真正「終結」。直到所有任務都執行完畢了,調用 isTerminated() 方法才會返回 true,這表示線程池已關閉並且線程池內部是空的,所有剩餘的任務都執行完畢了。

awaitTermination()

第四個方法叫作 awaitTermination(),它本身並不是用來關閉線程池的,而是主要用來判斷線程池狀態的。比如我們給 awaitTermination 方法傳入的參數是 10 秒,那麼它就會陷入 10 秒鐘的等待,直到發生以下三種情況之一:

等待期間(包括進入等待狀態之前)線程池已關閉並且所有已提交的任務(包括正在執行的和隊列中等待的都執行完畢,相當於線程池已經「終結」了,方法便會返回true 等待超時時間到後,第一種線程池「終結」的情況始終未發生,方法返回 false 等待期間線程被中斷,方法會拋出 Interruptedexception異常

等待期間(包括進入等待狀態之前)線程池已關閉並且所有已提交的任務(包括正在執行的和隊列中等待的)都執行完畢,相當於線程池已經「終結」了,方法便會返回 true;

等待超時時間到後,第一種線程池「終結」的情況始終未發生,方法返回 false;等待期間線程被中斷,方法會拋出 InterruptedException 異常。

shutdownNow()

最後一個方法是 shutdownNow(),也是 5 種方法裡功能最強大的,它與第一種 shutdown 方法不同之處在於名字中多了一個單詞 Now,也就是表示立刻關閉的意思。在執行 shutdownNow 方法之後,首先會給所有線程池中的線程發送 interrupt 中斷信號,嘗試中斷這些任務的執行,然後會將任務隊列中正在等待的所有任務轉移到一個 List 中並返回,我們可以根據返回的任務 List 來進行一些補救的操作,例如記錄在案並在後期重試。

public List<Runnable> shutdownNow() {      List<Runnable> tasks;     final ReentrantLock mainLock = this.mainLock;     mainLock.lock();      try {          checkShutdownAccess();         advanceRunState(STOP);         interruptWorkers();         tasks = drainQueue();     } finally {          mainLock.unlock();     }        tryTerminate();     return tasks;  } 

源碼中有一行 interruptWorkers() 代碼,這行代碼會讓每一個已經啟動的線程都中斷,這樣線程就可以在執行任務期間檢測到中斷信號並進行相應的處理,提前結束任務。這裡需要注意的是,由於 Java 中不推薦強行停止線程的機制的限制,即便我們調用了 shutdownNow 方法,如果被中斷的線程對於中斷信號不理不睬,那麼依然有可能導致任務不會停止。

總結

中斷和關閉線程的方式五花八門,看起來很相似,其實裡頭大有門道。處理不好,可是會導致程序崩潰的。

【編輯推薦】

【責任編輯:

武曉燕

TEL:(010)68476606】

點讚 0

相關焦點

  • 又是如何中斷線程?
    即程序不是在任何時候停頓下來進行GC,只有到了安全點才去更新OopMap和停頓,等待GC完成在繼續執行。2、分析有了OopMap,HotSpot就能很快的完成GC Roots的枚舉了。三、讓線程停下來的兩種方法1、搶斷式中斷在GC發生時,中斷直接所有線程,發現沒有在安全點的,再恢復線程讓他跑到安全點。現在幾乎沒有虛擬機採用這種方式。
  • 【國際服】我看到你了——中斷自爆怪可視化攻略
    你猜的沒錯,獅子現在就是在刷那個該死的迅發電漿炮(天王星中斷怪掉落),我都斷斷續續的刷了三天了,還是沒出QAQ,我太難了,倘若今天再不出我就氪金買了算了。正因刷不出而和沙雕群友們吐槽之際,獅子發現大部分小夥伴對於如何尋找中斷自爆怪還停留在自己帶耳機聽滴滴滴聲音,或者死記每個地形中斷怪出現的路線。
  • Java中的多線程你只要看這一篇就夠了
    ●多線程:指的是這個程序(一個進程)運行時產生了不止一個線程●並行與並發:●並行:多個cpu實例或者多臺機器同時執行一段處理邏輯,是真正的同時。●並發:通過cpu調度算法,讓用戶看上去同時執行,實際上從cpu操作層面不是真正的同時。
  • 吹爆系列:探索 Android 多線程的一切
    這時我們就把線程 A 叫做線程 B 的父線程,把線程 B 叫做線程 A 的子線程。1.3 線程的六個方法線程的常用方法有六個,它們分別是三個非靜態方法 start()、run()、join() 和三個靜態方法 currentThread()、yield()、sleep() 。
  • 單片機中斷與CPU的輪詢有什麼區別
    中斷:中斷是一種硬體機制,在這種機制中,設備會通知CPU它需要引起注意。中斷可以隨時發生。因此,當CPU通過指示中斷請求線收到中斷信號時,CPU停止當前進程並通過將控制權傳遞給服務設備的中斷處理程序來響應該中斷。輪詢:輪詢不是一種硬體機制,它是一種協議,CPU會穩定地檢查該設備是否需要注意。無論設備告訴處理單元它希望進行硬體處理的位置如何,在輪詢過程中,處理單元都會不斷詢問I/O設備是否希望進行CPU處理。
  • Java8線程池ThreadPoolExecutor底層原理及其源碼解析
    4.2.4 線程池中的線程集合private final HashSet<Worker> workers = new HashSet<Worker>();用來保存當前線程池中的所有線程;可通過該集合對線程池中的線程進行中斷
  • Java項目實踐,CountDownLatch實現多線程閉鎖
    摘要本文主要介紹Java多線程並發中閉鎖(Latch)的基本概念、原理、示例代碼、應用場景,通過學習,可以掌握多線程並發時閉鎖(Latch)的使用方法。概念「閉鎖」就是指一個被鎖住了的門將線程a擋在了門外(等待執行),只有當門打開後(其他線程執行完畢),門上的鎖才會被打開,a才能夠繼續執行。
  • Gmail、YouTube等谷歌旗下服務突發全球中斷
    截至目前,谷歌已修復了中斷問題,各項服務已可正常提供。要點谷歌瀏覽器個人信息中心板塊Workspace Status Dashboard顯示,在經歷大範圍中斷後,其所有主要服務都已恢復正常。如此看來,中斷事故可能影響的是谷歌帳戶,而不是服務本身。
  • 解決遊戲網絡中斷及掉線問題
    網絡連接中斷,是DNF裡面經常遇見的事情,俗稱六字真言有時正當我們玩做著任務,或者正在打團,刷深淵的時候出來這個,是很揪心的,那麼今天勤帝,教大家一些方法,純屬個人經驗,不喜勿噴可能是服務的問題,最近跨區交易開通了,伺服器不穩定,有的人經常這樣,這個沒法解決,只能多等騰訊更新幾次就會慢慢穩定了
  • 命運線最忌是中斷
    且暗示著不是靠自力,而是靠朋友或配偶的幫助來把握住幸運。  命運線起自手腕,穿過生命線往上升的人,表示要為父母犧牲,所以自己的命運就受到父母的左右。  命運張像蛇形般上升的人,主心志不堅,生活不安定,若僅靠本身力量發展,恐難有成就、且缺乏子運。  命運線由點線連接而成的人,主意志薄弱、缺乏勤勉努力,無論作任何事、都不能有始有終。
  • 一文講透 「進程、線程、協程」
    單核CPU雙進程的情況進程直接特定的機制和遇到I/O中斷的情況下,進行上下文切換,輪流使用CPU資源雙核CPU雙進程的情況(python的多線程是偽多線程,下文中將詳細介紹)什麼是協程協程(Coroutine,又稱微線程)是一種比線程更加輕量級的存在,協程不是被作業系統內核所管理,而完全是由程序所控制。協程與線程以及進程的關係見下圖所示。
  • 張緒山|中華文明是世界唯一未曾中斷的嗎?
    近些年「四大文明」說雖已不太流行,但與之相關的「中國文明是世界上唯一沒有中斷的古老文明」的傳統說法,卻仍被很多人堅持,尤其是在國學界,似仍被奉為不易之論,頻繁見於眾多史學著作,包括一些著名史學家的著述。這裡首先需要弄清楚的,是「族群徵服」與「文明中斷」的關係。一般而言,造成文明中斷與消失的原因,不外自然災難與族群徵服。
  • 51單片機中中斷程序大全(源程序)
    //啟動定時器T0Countor=0;            //從0開始累計中斷次數while(1);}/**************************************************************函數功能:定時器T0的中斷服務程序*********************************
  • 烏克蘭中斷俄羅斯主流電視頻道轉播
    烏克蘭電視與廣播委員會新聞局發布消息稱,所有烏克蘭電視服務運營商需要中斷對俄羅斯電視頻道的轉播,包括俄羅斯「Vesti」電視臺,「俄羅斯24小時」電視臺,俄羅斯第一頻道,俄羅斯獨立電視臺等,這些都是俄羅斯主要國有電視媒體。 據悉,截至當地時間3月11日中午12點,約有50%的烏克蘭電視網絡運營商中斷了俄羅斯電視頻道的轉播,還有一些正在準備切斷。
  • 三菱FX5U PLC中斷的使用和案例!
    我們都知道要實現中斷功能,首先要向PLC發出中斷請求信號,而發出中斷信號的設備就稱為中斷源。,包括輸入中斷、高速比較一致中斷、內部定時器中斷、來自模塊的中斷。今天就給大家講一下這幾種類型的中斷的使用和案例。一、輸入中斷輸入中斷是硬體信號中斷,輸入中斷指針對應輸入軟元件X,當外部輸入信號接通時,會立刻執行對應指針的中斷程序。輸入中斷常用於外部緊急事件的處理,如報警。下面利用輸入中斷做急停報警功能。
  • 行政訴訟中不存在時效的中止、中斷情形
    行政訴訟法並未規定訴訟時效的中止、中斷的情形,當事人因信訪等原因導致逾期起訴的,或因為不清楚法律規定而逾期起訴的,是歸責於當事人自身的原因,並不是可以延長起訴期限的法定理由。上訴人關於其因多次找有關部門進行申訴,其訴訟時效因其主張權利發生中斷,以及被上訴人對該事件的處理表明其放棄了訴訟時效抗辯權的主張,沒有法律依據,不能成立,本院不予支持。
  • 鴻蒙內核源碼分析:Task/線程管理篇
    ,線程是競爭系統資源的最小運行單元。線程可以使用或等待CPU、使用內存空間等系統資源,並獨立於其它線程運行。鴻蒙內核每個進程內的線程獨立運行、獨立調度,當前進程內線程的調度不受其它進程內線程的影響。鴻蒙內核中的線程採用搶佔式調度機制,同時支持時間片輪轉調度和FIFO調度方式。鴻蒙內核的線程一共有32個優先級(0-31),最高優先級為0,最低優先級為31。
  • AT91RM9200 PIO中斷在短波通信系統中的應用
    摘要:為擴展AT9lRM9200中斷處理能力,將通用IO配置為中斷輸入,並針對負脈衝中斷信號的二次響應問題提出2種優化解決方案。實驗表明,在成功解決了中斷二次響應的問題基礎上,滿足系統的實時性要求。
  • 最全面的Java多線程用法解析
    最全面的java多線程用法解析,如果你對Java的多線程機制並沒有深入的研究,那麼本文可以幫助你更透徹地理解Java多線程的原理以及使用方法。1.創建線程在Java中創建線程有兩種方法:使用Thread類和使用Runnable接口。在使用Runnable接口時需要建立一個Thread實例。
  • showstopper不是中斷演出的人,而是指表演中精彩的部分
    這個詞光看字面,會以為是某個中斷表演的人吧?其實,showstopper描述的是表演者的演出博得滿堂喝彩,觀眾的掌聲實在太過熱烈,以至於中斷了演出。後來被用來形容「表演中的精華橋段」。凱莉:你參加昨晚的年度晚會了嗎?埃爾頓:參加了啊,晚會很棒你沒去嗎?凱莉:我們的團隊剛好在主持一個活動,所以都沒去。埃爾頓:太可惜了!創辦人的演講成了整晚的焦點。凱莉:真希望有人把演講錄下來。