Java多線程系列--線程間的狀態轉換

前言

Java中的線程的生命週期大體可分爲5種狀態。具體是哪5種呢,讓我們來一起了解一下。


敘述

首先我們通過一張圖來看一下Java中線程的生命週期。
在這裏插入圖片描述

線程狀態

新建狀態(New)

新創建了一個線程對象,但還沒有調用start()方法。如:Thread t = new MyThread();

就緒狀態(Runnable)

當調用線程對象的start()方法(t.start();),線程即進入就緒(等待)狀態。處於就緒狀態的線程,只是說明此線程已經做好了準備,隨時等待CPU調度執行,並不是說執行了t.start()此線程立即就會執行;

處於就緒狀態的線程並不一定立即運行run()方法,線程還必須同其他線程競爭CPU時間,只有獲得CPU時間纔可以運行線程。因爲在單CPU的計算機系統中,不可能同時運行多個線程,一個時刻僅有一個線程處於運行狀態。因此此時可能有多個線程處於就緒狀態。對多個處於就緒狀態的線程是由Java運行時系統的線程調度程序(thread scheduler)來調度的。

運行狀態(Running)

當CPU開始調度處於就緒狀態的線程時,此時線程才得以真正執行,即進入到運行狀態。注:就 緒狀態是進入到運行狀態的唯一入口,也就是說,線程要想進入運行狀態執行,首先必須處於就緒狀態中;

阻塞狀態(Blocked)

處於運行狀態中的線程由於某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU調用以進入到運行狀態。根據阻塞產生的原因不同,阻塞狀態又可以分爲三種:

1.等待阻塞:運行狀態中的線程執行wait()方法,使本線程進入到等待阻塞狀態,JVM會把該線程放入等待隊列(waitting queue)中;

2.同步阻塞 – 線程在獲取synchronized同步鎖失敗(因爲鎖被其它線程所佔用),它會進入同步阻塞狀態,JVM會把該線程放入鎖池(lock pool)中;

3.其他阻塞 – 通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。

死亡狀態(Dead)

線程run()、main() 方法執行結束或者因異常退出了run()方法,該線程結束生命週期。死亡的線程不可再次復生。


狀態之間的轉換

就緒狀態轉換爲運行狀態:當此線程得到處理器資源;

運行狀態轉換爲就緒狀態:當此線程主動調用yield()方法或在運行過程中失去處理器資源。

運行狀態轉換爲死亡狀態:當此線程線程執行體執行完畢或發生了異常。

此處需要特別注意的是:當調用線程的yield()方法時,線程從運行狀態轉換爲就緒狀態,但接下來CPU調度就緒狀態中的哪個線程具有一定的隨機性,因此,可能會出現A線程調用了yield()方法後,接下來CPU仍然調度了A線程的情況。

幾個方法的比較

1. Thread.sleep(long millis),當前線程調用此方法,當前線程進入阻塞,但不釋放對象鎖,millis後線程自動甦醒進入可運行狀態。作用:給其它線程執行機會的最佳方式。

2. Thread.yield(),當前線程調用此方法,當前線程放棄獲取的cpu時間片,由運行狀態變會可運行狀態,讓OS再次選擇線程。作用:讓相同優先級的線程輪流執行,但並不保證一定會輪流執行。實際中無法保證yield()達到讓步目的,因爲讓步的線程還有可能被線程調度程序再次選中。Thread.yield()不會導致阻塞。

3. t.join()/t.join(long millis),當前線程裏調用其它線程1的join方法,當前線程阻塞,但不釋放對象鎖,直到線程1執行完畢或者millis時間到,當前線程進入可運行狀態。

4. obj.wait(),當前線程調用對象的wait()方法,當前線程釋放對象鎖,進入等待隊列。依靠notify()/notifyAll()喚醒或者wait(long timeout)timeout時間到自動喚醒。

5. obj.notify()喚醒在此對象監視器上等待的單個線程,選擇是任意性的。notifyAll()喚醒在此對象監視器上等待的所有線程。


等待隊列
調用obj的wait(), notify()方法前,必須獲得obj鎖,也就是必須寫在synchronized(obj) 代碼段內。

與等待隊列相關的步驟和圖如下:

1.線程1獲取對象A的鎖,正在使用對象A。
2.線程1調用對象A的wait()方法。
3.線程1釋放對象A的鎖,並馬上進入等待隊列。
4.鎖池裏面的對象爭搶對象A的鎖。
5.線程5獲得對象A的鎖,進入synchronized塊,使用對象A。
6.線程5調用對象A的notifyAll()方法,喚醒所有線程,所有線程進入鎖池。線程5調用對象A的notify()方法,喚醒一個線程,不知道會喚醒誰,被喚醒的那個線程進入鎖池。
7.notifyAll()方法所在synchronized結束,線程5釋放對象A的鎖。
8.鎖池裏面的線程爭搶對象鎖,但線程1什麼時候能搶到就不知道了。 原本鎖池+第6步被喚醒的線程一起爭搶對象鎖。
在這裏插入圖片描述

鎖池狀態

1.當前線程想調用對象A的同步方法時,發現對象A的鎖被別的線程佔有,此時當前線程進入鎖池狀態。簡言之,鎖池裏面放的都是想爭奪對象鎖的線程。
2.當一個線程1被另外一個線程2喚醒時,1線程進入鎖池狀態,去爭奪對象鎖。
3.鎖池是在同步的環境下才有的概念,一個對象對應一個鎖池。

小結

感謝您的閱讀~~

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