更新時間:2019-08-12 10:42:43 來源:動力節點 瀏覽2892次
線程休眠和中斷
我們知道了在編程過程中創建線程,并啟動以后,線程會交由操作系統來管理調度執行一個我們指定的計算任務。
如果沒有其它異常情況出現的話,它會持續運行直到我們實現的run()方法執行完畢為止。
那么我們通常在什么情況下需要創建單獨的線程呢?一般是在我們的程序需要執行一個比較耗時的任務,或者需要一個程序不斷的重復執行某種動作,這些基本上都屬于我們程序的后臺來負責處理的問題,比如檢查某種狀態或者動作,而不影響其它任務的執行時,需要創建單獨的線程來操作。
耗時較長的工作比如需要掃描某個特定的目錄結構中所有文件,到數據庫中查詢一個大容量的數據表等。
需要持續的周期性間隔的查看某個狀態和結果的任務,雖然我們可以直接在借助while循環來執行,但是如此會浪費太多的CPU時間,因為我們的CPU會一次又一次的被切換占用來執行它。這時我們需要讓監控循環能夠等待一定時間間隔再檢查,從而減少CPU無效時間的占用。
對于此類用例,更好的方法是讓執行監控任務的線程能夠暫停執行特定間隔,再次期間CPU可以去處理其它計算任務。
這種情況我們可以調用類java.lang.Thread的sleep()方法來實現,如下例所示:
調用sleep()使當前線程進入睡眠狀態,而不消耗任何處理時間。
該方法被調用意味著當前線程從活動線程列表中刪除自己,調度程序在下一次執行時不會調度它直到指定的毫秒數過后才會再次調度它執行。
這里需要注意的是我們傳遞給sleep()方法的時間只能是作為調度程序的一個指示,實際的執行不一定會絕對準確的按照指定的時間進行。
主要是因為操作系統在執行調度時,實際執行時可能會出現線程提前或延遲幾納秒或幾毫秒返回的情況。
因此,我們不建議將sleep()方法用于對時間精度有比較高要求的實時調度上。但是對于大多數情況來說,在這種納秒或毫秒級別達到的精度已經足夠了。
線程的中斷現象
因為中斷是操作系統調度CPU任務執行的一個重要機制,是線程交互的一個非常基本的特性,可以理解為一個線程向另一個線程發送的簡單中斷消息。
我們知道中斷機制是通過中斷異常來提供處理入口的。
所以,當我們在一個線程上調用sleep()方法使其處于睡眠狀態時,它可能會被中斷,表現為拋出InterruptedException異常。
在上面的代碼示例中,您可能已經注意到sleep()可能拋出InterruptedException。
而關聯線程可以通過調用Thread.interrupted()方法來檢查自己是否被中斷了。
或者當它將時間花費在sleep()這樣的方法中時就是隱式的中斷,也會在中斷時拋出異常。
讓我們用下面的代碼例子來仔細看看中斷:
簡單分析一下上面的示例代碼,我們在main方法中,首先啟動一個新線程myThread,如果不中斷它,它將休眠很長時間(大約290年)。
這當然不能這樣下去,所以為了能提前完成程序執行,myThread線程通過在main方法中調用它的實例方法interrupt()來執行中斷。
由于myThread線程從開始執行就處在sleep()調用中,調用interrupt()方法結果導致InterruptedException異常發生,被catch捕獲,從而在控制臺上打印為“myThread線程被異常中斷!”
然后繼續執行while循環檢查該線程是否被中斷,如果沒有就執行空循環,如果被中斷就繼續執行完該線程任務,即執行打印“myThread線程被第二次中斷”。
我們在主線程中多次執行sleep()方法是給予子線程執行機會。
在記錄了異常之后,線程會忙著等待,直到設置了線程上的中斷標志。
這也是通過調用線程實例變量上的interrupt()從主線程設置的。
總的來說,我們看到控制臺的輸出如下:
從輸出中可以看到,調度程序在再次啟動myThread之前已經執行了主線程。
因此,myThread在主線程開始休眠后打印出異常的接收。
其實,在使用多個線程編程時,通常我們會發現線程的日志輸出在某種程度上難以預測,因為很難計算接下來執行哪個線程。
當我們必須處理更多的線程時,我們就會發現而這些線程的暫停并沒有像上面的示例中那樣符合預期,常常情況會變得很糟。
在這些情況下,整個程序得到某種內部動態,這使得并發編程成為一項具有挑戰性的任務。
join線程
前面我們看到的,在線程執行過程中,我們可以讓線程休眠,直到它被另一個線程喚醒。這能夠充分的利用CPU的處理時間。
由于在大量線程同時執行時,線程的暫停已經無法準確的調度線程執行,所以迫切需要線程的另一個重要特性就是線程能夠等待另一個線程的終止。
也就是說將線程按照某種先后順序排列起來中。
這里假設我們必須實現某種數字處理操作,該操作可以劃分為多個并行運行的線程。
啟動所謂工作線程的主線程必須等待,直到所有子線程都終止。
在我們的主方法中,我們創建了一個由5個線程組成的數組,這些線程都是一個接一個地啟動的。
我們先看一下在不使用join()方法的執行情況:
我們可以看到,主線程率先執行完成,然后才是各個子線程依次完成。
而當我們增加Join調用時:
一旦啟動它們,我們就在主線程中等待它們的終止。線程本身通過計算一個接一個的隨機數來模擬一些數字處理。
一旦完成,就打印出“線程結束!”。最后主線程承認所有子線程的終止:
我們將觀察到,“完成”消息的順序因執行而異。如果多次執行該程序,我們可能會發現最先完成的線程并不總是相同的。但是,最后一條語句始終是等待它的子線程的主線程。
觀察上面兩次執行結果,我們發現join()的作用就是讓主線程等待子線程的執行,而不是采用使主線程休眠的方式。
這種處理主要用在在某些情況下,我們將不得不等待線程的結束。例如,我們可能有一個程序,它將開始初始化它需要的資源,然后再執行其余的執行。我們可以用多個獨立線程的形式運行初始化任務,并等待這些初始化線程執行完成后再開始繼續執行程序的其余部分。
為此,我們可以使用Thread類的join()方法。當我們使用Thread對象調用此方法時,它將暫停調用線程的執行,直到被調用的對象完成執行。
總結
對于線程的調度管理,始終是并發編程中最重要的課題,通過上面的說明我們一定要記住,sleep()雖然能夠讓線程休眠,但是我們提供的休眠時間參數只是個參考值,并不能精確的被采用,而join()方法,主要用于讓父線程等待子線程結束之后才能繼續運行。
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習