執行緒的阻塞和喚醒在多執行緒併發過程中是一個關鍵點,當執行緒數量達到很大的數量級時,併發可能帶來很多隱蔽的問題。如何正確暫停一個執行緒,暫停後又如何在一個要求的時間點恢復,這些都需要仔細考慮的細節。Java為我們提供了多種API來對執行緒進行阻塞和喚醒操作,比如suspend與resume、sleep、wait與notify以及park與unpark等等。
睡眠控制執行緒阻塞與喚醒的最簡單方式就是sleep了,Java透過sleep(n)方法能讓執行緒進入到阻塞等待狀態,直到休眠時間達到指定值後自動喚醒。該方法簡單也常用,但這種方式比較死板,需要我們預先確定執行緒進入阻塞的時間。而有些場景實際上我們根本沒辦法確定睡眠時間,這是sleep方式的最大劣勢。
sleep的使用很簡單,下面為一個例子。讓當前執行緒睡眠2000ms,最終輸出為"Sleep time in ms = 2000"。
掛起與恢復在Java發展史上曾經使用suspend()、resume()方法對於執行緒進行阻塞喚醒,它能夠在程式碼中控制阻塞和喚醒的時間節點,比起sleep()方法更加靈活。比如執行緒啟動後在某個時間點需要讓它掛起,這可以使用suspend方法,而當要重新喚醒它時則使用resume方法。
注意:suspend(),resume(),stop()這樣的方法都被標註為過期方法,因為其不會保證釋放資源,容易產生死鎖,所以不建議使用。
死鎖問題為什麼會產生上面的現象呢?其實是由死鎖導致。乍一看感覺一點問題都沒有,執行緒的任務僅僅只是簡單地列印字串。其實問題的根源隱藏得較深,主執行緒啟動了執行緒mt後,執行緒mt開始執行execute()方法,不斷列印字串。
問題就出現在System.out.println,由於println被宣告為一個同步方法,執行時將對System類的out(PrintStream類的一個例項)單例屬性加同步鎖。而suspend()方法掛起執行緒但並不釋放鎖,線上程mt被掛起後主執行緒呼叫System.out.println同樣需要獲取System類ut物件的同步鎖才能列印“can you get here?”。主執行緒就一直在等待同步鎖而mt執行緒不釋放鎖,這就導致了死鎖的產生。