摘要: 原創出處 https://studyidea.cn 「公眾號:程式通事 」歡迎關註和轉載,保留摘要,謝謝! 使用 Java 阻塞 I/O 模型讀取數據,將會導致線程阻塞,線程將會進入休眠,從而讓出 CPU 的執行權,直到數據讀取完成。這個期間如果使用 jstack 查看線程狀態,卻可以發現J ...
摘要: 原創出處 https://studyidea.cn 「公眾號:程式通事 」歡迎關註和轉載,保留摘要,謝謝!
使用 Java 阻塞 I/O 模型讀取數據,將會導致線程阻塞,線程將會進入休眠,從而讓出 CPU 的執行權,直到數據讀取完成。這個期間如果使用 jstack 查看線程狀態,卻可以發現Java 線程狀態是處於 RUNNABLE,這就和上面說的存在矛盾,為什麼會這樣?
上面的矛盾其實是混淆了操作系統線程狀態與 Java 線程狀態。這裡說的線程阻塞進入休眠狀態,其實是操作系統層麵線程實際狀態。而我們使用 jstack 查看的線程狀態卻是 JVM 中的線程狀態。
線程是操作系統中一種概念,Java 對其進行了封裝,Java 線程本質上就是操作系統的中線程,其狀態與操作系統的狀態大致相同,但還是存在一些區別。
下麵首先來看我們熟悉的 Java 線程狀態。
Java 線程狀態
Java 線程狀態定義在 Thread.State
枚舉中,使用 thread#getState
方法可以獲取當前線程的狀態。
Thread.State
狀態如下圖:
可以看到 Java 線程總共存在 6 中狀態,分別為:
- NEW(初始狀態)
- RUNNABLE(運行狀態)
- BLOCKED(阻塞狀態)
- WATTING(等待狀態)
- TIMED_WAITING(限時等待狀態)
- TERMINATED(終止狀態)
NEW(初始狀態)與 RUNNABLE(運行狀態)
每個使用 new Thread()
剛創建出線程實例狀態處於 NEW 狀態,一旦調用 thread.start()
,線程狀態將會變成 RUNNABLE。
RUNNABLE(運行狀態) 與 BLOCKED(阻塞狀態)
RUNNABLE 狀態的線程在進入由 synchronized
修飾的方法或代碼塊前將會嘗試獲取一把隱式的排他鎖,一旦獲取不到,線程狀態將會變成 BLOCKED,等待獲取鎖。一旦有其他線程釋放這把鎖,線程成功搶到該鎖,線程狀態就將會從 BLOCKED 轉變為 RUNNABLE 狀態。
RUNNABLE(運行狀態) 與 WATTING(等待狀態)
處於 WATTING 狀態的線程將會一直處於無限期的等待狀態,需要等待其他線程喚醒。總共存在三種方法將會使線程從 RUNNABLE 變成 WATTING。
Object#wait
線程在獲取到 synchronized
隱式鎖後,顯示的調用 Object#wait()
方法。這種情況下該線程將會讓出隱式鎖,一旦其他線程獲取到該鎖,且調用了 Object.notify()
或object.notifyAll()
,線程將會喚醒,然後變成 RUNNABLE。
Thread#join
join
方法是一種線程同步方法。假設我們在 main 方法中執行 Thread A.join() 方法,main 線程狀態就會變成 WATTING。直到 A 線程執行完畢,main 線程才會再變成 RUNNABLE。
LockSupport#park()
LockSupport 是 JDK 併發包里重要對象,很多鎖的實現都依靠該對象。一旦調用 LockSupport#park()
,線程就將會變為 WATTING 狀態。如果需要喚醒線程就需要調用 LockSupport#unpark,然後線程狀態重新變為 RUNNABLE。
RUNNABLE(運行狀態) 與 TIMED_WAITING(限時等待狀態)
TIMED_WAITING 與 WATTING 功能一樣,只不過前者增加限時等待的功能,一旦等待時間超時,線程狀態自動變為 RUNNABLE。以下幾種情況將會觸發這種狀態:
Thread#sleep(long millis)
- 占有 synchronized 隱式鎖的線程調用
Object.wait (long timeout)
方法 Thread#join (long millis)
LockSupport#parkNanos (Object blocker, long deadline)
LockSupport#parkUntil (long deadline)
RUNNABLE(運行狀態)與 TERMINATED(終止狀態)
線程一旦執行結束或者線程執行過程發生異常且未正常捕獲處理,狀態都將會自動變成 TERMINATED。
Java 線程 6 種狀態看起來挺複雜的,但其實上面 BLOCKED,WATTING,TIMED_WAITING,都會使線程處於休眠狀態,所以我們將這三類都歸類為休眠狀態。這麼分類的話,Java 線程生命周期就可以簡化為下圖:
通用操作系統線程狀態
上面講完 Java 系統的線程狀態,我們來看下通用操作系統的線程狀態。操作系統線程狀態可以分為初始狀態,可運行狀態,運行狀態,休眠狀態以及終止狀態,如下圖:
這 5 中狀態詳細情況如下:
- 初始狀態,這時候線程剛被創建,還不能分配 CPU 。
- 可運行狀態,線程等待系統分配 CPU ,從而執行任務。
- 運行狀態,操作系統將 CPU 分配給線程,線程執行任務。
- 休眠狀態,運行狀態下的線程如果調用阻塞 API,如阻塞方式讀取文件, 線程狀態就將變成休眠狀態。這種情況下,線程將會讓出 CPU 使用權。休眠結束,線程狀態將會先變成可運行狀態。
- 線程執行結束或者執行過程發生異常將會使線程進入終止狀態,這個狀態下線程使命已經結束。
對比兩者線程狀態
比較 Java 線程與操作系統線程,可以發現 Java 線程狀態沒有可運行狀態。也就是說 Java 線程 RUNNABLE 狀態包括了操作系統的可運行狀態與運行狀態。一個處於 RUNNABLE 狀態 Java 線程,在操作系統層面狀態可能為可運行狀態,正在等待系統分配 CPU 使用權。
另外 Java 線程細分了操作系統休眠狀態,分成了 BLOCKED,WATTING,TIMED_WAITING 三種。
當線程調用阻塞式 API,線程進入休眠狀態,這裡指的是操作系統層面的。從 JVM 層面,Java 線程狀態依然處於 RUNNABLE 狀態。JVM 並不關心操作系統線程實際狀態。從 JVM 看來等待 CPU 使用權(操作系統線程狀態為可運行狀態)與等待 I/O (操作系統線程狀態處於休眠狀態)沒有區別,都是在等待某種資源,所以都歸入 RUNNABLE 狀態。
其他 Java 線程狀態與操作線程狀態類似。
面試官:都說阻塞 I/O 模型將會使線程休眠,為什麼 Java 線程狀態卻是 RUNNABLE?