線程與操作系統中線程(進程)的概念同根同源,盡管千差萬別。
操作系統中有狀態以及狀態的切換,Java線程中照樣也有。
State
在Thread類中有內部類 枚舉State,用於抽象描述Java線程的狀態,共有6種不同的狀態
詳細定義如下:
public enum State {
/**
* 至今尚未啟動的線程的狀態。
*/
NEW,
/**
* 可運行線程的線程狀態。
* 處於可運行狀態的某一線程正在 Java 虛擬機中運行,但它可能正在等待操作系統中的其他資源,比如處理器。
*/
RUNNABLE,
/**
* 受阻塞並且正在等待監視器鎖的某一線程的線程狀態。
* 處於受阻塞狀態的某一線程正在等待進入一個同步的塊/方法的監視器鎖,或者在調用 Object.wait 之后再次進入同步的塊/方法。
*/
BLOCKED,
/**
* 某一等待線程的線程狀態。
* 某一線程因為調用下列方法之一而處於等待狀態:
* 不帶超時值的 Object.wait
* 不帶超時值的 Thread.join
* LockSupport.park
* 處於等待狀態的線程正等待另一個線程,以執行特定操作。
* 例如,已經在某一對象上調用了 Object.wait() 的線程正等待另一個線程,以便在該對象上調用 Object.notify() 或 Object.notifyAll()。
* 已經調用了 Thread.join() 的線程正在等待指定線程終止。
*/
WAITING,
/**
* 具有指定等待時間的某一等待線程的線程狀態。
* 某一線程因為調用以下帶有指定正等待時間的方法之一而處於定時等待狀態:
* Thread.sleep
* 帶有超時值的 Object.wait
* 帶有超時值的 Thread.join
* LockSupport.parkNanos
* LockSupport.parkUntil
*/
TIMED_WAITING,
/**
* 已終止線程的線程狀態。線程已經結束執行。
*/
TERMINATED;
}
狀態詳解
NEW
當一個線程創建后,也就是new了一個Thread,那么這個Thread的state就是NEW
有且只有這種情況下,才為NEW,不會從任何狀態轉換而來
也就是說如果一個線程狀態已經不再是NEW,那么他永遠不可能再重新回到NEW的狀態,這是一個起點
下面的示例中創建了一個線程myThread,並沒有調用start方法
TERMINATED
當一個線程終止后,就進入狀態TERMINATED
TERMINATED作為線程的終點,一旦進入TERMINATED狀態,將不再能夠轉換為其他狀態
下面的示例中,創建了一個線程myThread,並且調用start方法啟動
然后主線程(當前線程)sleep 1秒(確保myThread肯定結束了),然后查看myThread的狀態,很顯然,此時已經進入終止狀態
NEW和TERMINATED分別對應線程生命周期的起點和終點
對於NEW來說,一旦離開,就永遠回不來了;
對於TERMINATED來說,一旦到達, 就永遠回不去了;
RUNNABLE
RUNNABLE用於表示可運行狀態
下面的代碼在主線程中運行,運行過程中是RUNNABLE狀態
API中有說到:“處於可運行狀態的某一線程正在 Java 虛擬機中運行,但它可能正在等待操作系統中的其他資源,比如處理器。”
也就是說一個RUNNABLE並不是一定正在運行
如果我們將線程運行所有的資源與條件分為兩種:CPU時間片以及除了時間片以外的所有其他;
一旦進入RUNNABLE狀態,那么他肯定已經擁有了“除了時間片以外的所有其他資源”
但是,是否正在被執行?這個不確定,還要看是否被分配了時間片
如果沒有處理器資源(時間片)那么就是“准備妥當”狀態,如果分配了處理器資源(時間片),那么就是“正在運行”狀態。
所以RUNNABLE狀態可以細分為兩種狀態:准備妥當(READY)與RUNNING(正在運行)
但是,為什么沒有將RUNNABLE細分?
很顯然,對於開發者來說能夠做到的就是“除了時間片以外的所有其他資源”,而對於操作系統處理器CPU時間片的調度,是完全沒有能力操控的(yield也只是提示)
所以,從應用的角度看,也就只有RUNNABLE狀態,一個RUNNABLE的線程,他隨時可能在運行,也可能在等待調度。
等待狀態
BLOCKED、WAITING、TIMED_WAITING三種狀態相對前面的幾種,相對稍微復雜一點,因為會涉及到各種切換
對照着漢字來說,這三者都有“等”的意思,但是卻又不太相同
舉幾個例子感受一下
當你發現前方信號燈轉變為紅燈時,你停車等待;
當你經過斑馬線時,正好有行人經過,你停車等待;
當售票窗口中午休息時,你原地等待;
這幾種等待更多的是因為不可抗力,不得不等的一種場景,BLOCKED更接近這種等待;
對於臨界資源的訪問,需要互斥訪問,Java中使用對象監視器作為鎖,想要進入同步區域,就需要獲得對應的監視器鎖
如果獲取不到,就需要等待,這就是BLOCKED狀態;
要出門時,你老婆說我化個妝,你等我下;
買水果時,售貨員說剛賣完了,師傅去倉庫去取了,您稍等一下;
此時的等,是在等一件事情的發生,WAITING更接近這種等待;
雖然都是在等待,卡住不能動,還是等一等,還是有區別的;
TIMED_WAITING與WAITING就比較相似了,他們的區別,從漢語的角度理解類似“你等我兩分鍾和你等一會”的區別
等兩分鍾有時間,等一會兒不確定到底等待多大一會兒
再回到Java線程中,可以認為:
- 如果一個線程在等待獲取進入同步區域的監視器鎖,那么是BLOCKED;
- 如果線程調用了不帶超時值的等待方法,比如 Object.wait,持續等待某件事情的發生,直到收到通知,那么是WAITING
- 如果線程調用了比如帶有超時值的等待方法,比如wait(long timeout),進行一定時間的等待,到到時間后將不再等待,那么是TIMED_WAITING
狀態轉換圖
換一個角度理解,線程狀態的切換
下圖從前驅和后繼的角度分析了線程狀態的變化
以中間一列為中心
狀態對比
既然操作系統中線程概念模型有狀態切換,Java線程也有狀態,他們有何異同?
如上圖所示,操作系統中的進程、線程模型的狀態
核心為就緒(ready)阻塞(waiting)執行(run)
而對於Java線程中
核心狀態為RUNNABLE、BLOCKED、WAITING、TIMED_WAITING(項目中根本不會創建線程,會借助於線程池,所以NEW和TERMINATED非重點)
Java線程為操作系統原生線程的映射,狀態上也是有所映射的
Runnable狀態,則對應了操作系統中的就緒(ready)和執行(run)
TIMED_WAITING ,WAITING還是BLOCKED,對應的都是操作系統線程的阻塞(waiting)狀態
需要注意的是:這些狀態是虛擬機狀態,它不反映任何操作系統的線程狀態,可以查看State的注釋
為什么狀態沒有對應?
我們之前在提及線程的實現時,就有說到用戶級和內核支持的對比,內核支持的是依靠操作系統來調度的,1.2之后就是對操作系統線程的映射
所以,既然調度依賴的是操作系統,那么,操作系統底層的狀態對於開發者來說就不是那么必要了,因為你並不能對他進行事無巨細的控制
JVM中的線程是操作系統底層線程的映射,既然是映射,可以認為是一個薄層封裝
封裝的目的是為了更好的符合Java多線程編程的模型,而不是要原模原樣的去照搬
從這一點也能更好地理解,為什么RUNNABLE相當於READY和RUNNING,因為JVM本來就只能做到這一步,READY還是RUNNING,搞不了,那還提供這兩個狀態干什么呢?
所以記住:
JVM中的狀態只是Java的多線程模型中的狀態,並不反應任何操作系統的線程狀態
JVM中的狀態與底層操作系統中線程的狀態也沒有必要去映射