一文搞定線程的生命週期

話不多說,直奔主題,看圖:
在這裏插入圖片描述
線程的6個狀態

  1. new:已經創建沒有調用start方法啓動,已經做好準備
  2. Runnable:調用start方法,可運行,即使在運行也是Runnable而不是running
  3. Blocked:monitor被其他線程佔有,也就是被synchronized修飾
  4. Waiting:一般習慣而言,把Blocked(被阻塞)、Waiting(等待)、Timed Waiting(計時等待)、都稱爲阻塞狀態,不僅僅是Blocked。
  5. Timed Waiting:記時等待
  6. Terminated:終止

注意
1.新創建只能往下走到可運行,而不能直接跳轉到其他狀態
2.線程生命週期不可回頭:一旦到了可運行就不能回到新創建,一旦終止,就不能再有任何狀態變化,所以一個線程只能有一次新創建和已終止。線程是不可以重複執行的,當它運行完了便會結束,一旦一個線程進入dead狀態,它便不可以重新回到Runnable等狀態,這個不可重複執行的性質和線程池一樣的,如果我們還想執行該任務,可以選擇重新創建一個線程,而原來的對象會被JVM回收

特殊情況:
1.如果發生異常,可以直接跳轉到終止TERMINATED,不必再遵循路徑。比如可以直接從Waiting到TERMINSTED
2.從Object.wait()剛被喚醒時,通常不能立即搶到monitor鎖,那麼會從waiting先進入blocked狀態,搶到鎖後再轉換到runnable狀態

Object類和線程先關的方法

/**
     *使當前線程等待,直到另外一個線程調用此對象的notify()方法或notifyAll()方法喚醒去繼續獲得cpu執行     
     *權,或者等待指定時間重新去繼續獲得cpu執行權,調用此方法會釋放鎖資源
     *
     * @param      millis   時間毫秒值
     * @throws  IllegalArgumentException      如果參數爲負數
     *               
     * @throws  IllegalMonitorStateException 
     *              如果當前線程不是對象監視器的所有者。
     * @throws  InterruptedException 
     *             如果被其他線程打斷報異常,而且清除中斷狀態
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#notifyAll()
     */
    public final void wait(long millis) throws InterruptedException {
        wait(millis, 0);
    }
    

    /**
     * 引起當前線程等待,釋放鎖,直到另一個線程爲此對象調用notify()方法或notifyAll()方法。線程繼續設               
     * 法獲得執行權
     * 
     * 此方法只能由鎖的獲得者的線程調用
     *
     * @throws  IllegalMonitorStateException 如果當前線程不是對象監視器的所有者。
     * @throws  InterruptedException 
     *               線程被其他線程打斷拋出異常,並清除中斷狀態
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#notifyAll()
     */
    @FastNative
    public final native void wait() throws InterruptedException;



/**
     * 隨機喚醒等待集中某一個線程
     *
     * @throws  IllegalMonitorStateException 如果當前線程不是此對象監視器的所有者。
     * @see        java.lang.Object#notifyAll()
     * @see        java.lang.Object#wait()
     */
    @FastNative
    public final native void notify();




/**
     * 喚醒等待集中所有線程
     *
     *
     * @throws  IllegalMonitorStateException  如果當前線程不是此對象監視器的所有者。
     *           
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#wait()
     */
    @FastNative
    public final native void notifyAll();

Wait原理圖:在這裏插入圖片描述
入口集Entry Set
等待集Wait Set

爲什麼wait必須在同步代碼塊中使用?
反過來思考如果不要求wait必須在同步代碼塊中調用,而是在外面調用會發生什麼?
比如消費者和生產者模式,如果消費者判斷沒有數據,還沒調用wait方法,而生產者已經生產而且已經調用notify方法,那麼消費者就會無限等待下去,而synchronized能避免出現這種情況

Wait/notify、sleep異同:
相同:
阻塞:都可以讓線程阻塞,對應線程狀態是waiting或者Time——waiting
響應中斷:都可以響應中斷
不同
Wait方法的執行必須在被synchronized修飾的代碼塊內,而sleep不需要
在同步方法裏面執行sleep方法時,不會釋放monitor鎖,但是,wait方法會釋放鎖
Sleep方法短暫的休眠之後會主動退出阻塞,而沒有指定的時間的wait方法則需要被其他線程喚醒或者中斷後才能退出阻塞
Wait()和notify(),notifyall()是object類的方法,sleep和yield方法Thread類中的方法

爲什麼線程通信的方法wait、notify、notifyall方法被定義在Object類中,而sleep方法被定義在Thread類中?

  1. 每個對象都可以上鎖,由於wait、notify、notifyall方法都是鎖級別的操作,所以把他們定義在Object類中,因爲鎖屬於對象
  2. Java的每個對象中都有一個鎖(monitor,也可以稱爲監視器)並且wait、notify等方法用於等待對象的鎖或者通知其他線程對象的監視器可用,在java中並沒有可供任何對象使用的鎖和同步器,這就是爲什麼這些方法是Object類中的一部分,在java的每一個類都有用於線程間通信的基本方法
  3. 一個很明顯的原因是java提供的鎖是對象級的而不是線程級的,每個對象都有鎖,通過線程獲得。如果線程需要等待某些鎖那麼調用對象中的wait方法就有意義了,如果wait()方法定義在Thread類中,線程正在等待哪個鎖就不明顯了
  4. 每個對象都擁有monitor(即鎖),所以當前線程等待某個對象的鎖,當然通過某個對象來操作了。而不是當前線程來操作,因爲當前線程會等待多個線程的鎖,如果對象通過線程來操作就非常複雜
  5. Wait和notify不僅僅是普通的方法和同步工具,更重要的是它們是java中兩個線程之間的通信機制,對語言設計者而言,如果不能通過java關鍵字(例如sychronized)實現通信機制,同時要確保這個機制對每個對象可用,那麼Object類則是正確的生聲明位置。同步和等待通知是;兩個不同的領域,不要看成相同和相關。同步時提供互斥並且和確保java類的線程安全,而wait和notify是兩個線程之間的通信機制
  6. 在java中爲了進入代碼的臨界區,線程需要鎖定並等待鎖定,他們不知道哪些線程持有鎖,而只是知道鎖被某個線程持有,並且他們應該等待取得鎖,而不是去了解哪個線程在同步塊內,並請求他們釋放鎖定

如何選擇用notify和notifyall?
Notify可能導致信號丟失這樣的正確性問題,而notifyall雖然效率不太高(把不需要喚醒的等待線程給喚醒了),但是其在正確性方面有保障。因此實現通知的一種比較流行的保守方法是優先使用notifyall以保障正確性,只有在證據表明使用notify足夠的情況下才使用notify—
條件1:一次通知喚醒至多一個線程。在不同的等待線程可能使用不同的保護條件的情況下,notify喚醒的一個任意線程可能並不是我們需要喚醒的那一個線程,因此這個問題還需要通過滿足條件二來排除
條件2:相應對象的等待集中僅包含同質等待線程。所謂同資等待線程指這些線程使用同一個保護條件,並且這些線程在wait調用返回之後的處理邏輯一致。最爲典型的同資線程是使用同一個Runnable接口創建不同的線程,或者從同一個Thread子類New出的多個實例
注意:notify喚醒的是其所屬對象的一個任意等待的線程,notify本身在喚醒線程是不考慮保護條件的。Notifyall方法喚醒的是其所屬對象上的所有等待線程。使用notify替代notifyall時需要確保一下兩個條件同時滿足:
1.一次通知僅需要喚醒至多一個線程
2.相應的對象上的所有等待線程是同質等待線程

Join:
作用、用法:因爲新的線程加入了我們,所以我們要等他執行完再出發
主線程等待副線程執行完畢,注意誰等誰
遇到中斷:
Join期間,線程到底是什麼狀態?:waiting,不是Time-waiting因爲join的時候無法預料實際等待時間是多少
注意點:
CountDownLatch或者CyclicBarrier類類似join的工具:
Join原理:
作用、用法:因爲新的線程加入了我們,所以我們要等他執行完再出發
主線程等待副線程執行完畢,注意誰等誰
遇到中斷:
Join期間,線程到底是什麼狀態?:waiting,不是Time-waiting因爲join的時候無法預料實際等待時間是多少
注意點:
CountDownLatch或者CyclicBarrier類類似join的工具:
Join原理:
底層調用wait(0),無限等待下去,但是沒有調用notify,每一個thread類run方法執行完畢後,自動notify

Yield:
作用:釋放我的CPU時間片,線程狀態依然是runnable,並不會釋放自己的鎖
定位:JVM不保證遵循,一般不適用,但是併發包裏面經常使用
和sleep區別:sleep期間線程調度器不會去調度該線程,而yield方法時只是讓線程釋放出自己的cpu時間片,線程任然處於就緒狀態,隨時可能再次被調度

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章