面試官:都說阻塞 I/O 模型將會使線程休眠,爲何 Java 線程狀態倒是 RUNNABLE?

摘要: 原創出處 https://studyidea.cn 「公衆號:程序通事 」歡迎關注和轉載,保留摘要,謝謝!

使用 Java 阻塞 I/O 模型讀取數據,將會致使線程阻塞,線程將會進入休眠,從而讓出 CPU 的執行權,直到數據讀取完成。這個期間若是使用 jstack 查看線程狀態,卻能夠發現Java 線程狀態是處於 RUNNABLE,這就和上面說的存在矛盾,爲何會這樣?html

上面的矛盾實際上是混淆了操做系統線程狀態與 Java 線程狀態。這裏說的線程阻塞進入休眠狀態,實際上是操做系統層面線程實際狀態。而咱們使用 jstack 查看的線程狀態倒是 JVM 中的線程狀態。java

線程是操做系統中一種概念,Java 對其進行了封裝,Java 線程本質上就是操做系統的中線程,其狀態與操做系統的狀態大體相同,但仍是存在一些區別。面試

下面首先來看咱們熟悉的 Java 線程狀態。併發

Java 線程狀態

Java 線程狀態定義在 Thread.State 枚舉中,使用 thread#getState 方法能夠獲取當前線程的狀態。ide

Thread.State 狀態以下圖idea

State.png

能夠看到 Java 線程總共存在 6 中狀態,分別爲:spa

  • NEW(初始狀態)
  • RUNNABLE(運行狀態)
  • BLOCKED(阻塞狀態)
  • WATTING(等待狀態)
  • TIMED_WAITING(限時等待狀態)
  • TERMINATED(終止狀態)

NEW(初始狀態)與 RUNNABLE(運行狀態)操作系統

每一個使用 new Thread() 剛建立出線程實例狀態處於 NEW 狀態,一旦調用 thread.start(),線程狀態將會變成 RUNNABLE線程

RUNNABLE(運行狀態) 與 BLOCKED(阻塞狀態)code

RUNNABLE 狀態的線程在進入由 synchronized修飾的方法或代碼塊前將會嘗試獲取一把隱式的排他鎖,一旦獲取不到,線程狀態將會變成 BLOCKED,等待獲取鎖。一旦有其餘線程釋放這把鎖,線程成功搶到該鎖,線程狀態就將會從 BLOCKED 轉變爲 RUNNABLE 狀態。

RUNNABLE(運行狀態) 與 WATTING(等待狀態)

處於 WATTING 狀態的線程將會一直處於無限期的等待狀態,須要等待其餘線程喚醒。總共存在三種方法將會使線程從 RUNNABLE 變成 WATTING

  1. Object#wait

線程在獲取到 synchronized 隱式鎖後,顯示的調用 Object#wait()方法。這種狀況下該線程將會讓出隱式鎖,一旦其餘線程獲取到該鎖,且調用了 Object.notify()object.notifyAll(),線程將會喚醒,而後變成 RUNNABLE

  1. Thread#join

join 方法是一種線程同步方法。假設咱們在 main 方法中執行 Thread A.join() 方法,main 線程狀態就會變成 WATTING。直到 A 線程執行完畢,main 線程纔會再變成 RUNNABLE

  1. LockSupport#park()

LockSupport 是 JDK 併發包裏重要對象,不少鎖的實現都依靠該對象。一旦調用 LockSupport#park(),線程就將會變爲 WATTING 狀態。若是須要喚醒線程就須要調用 LockSupport#unpark,而後線程狀態從新變爲 RUNNABLE

RUNNABLE(運行狀態) 與 TIMED_WAITING(限時等待狀態)

TIMED_WAITINGWATTING 功能同樣,只不過前者增長限時等待的功能,一旦等待時間超時,線程狀態自動變爲 RUNNABLE。如下幾種狀況將會觸發這種狀態:

  1. Thread#sleep(long millis)
  2. 佔有 synchronized 隱式鎖的線程調用 Object.wait (long timeout) 方法
  3. Thread#join (long millis)
  4. LockSupport#parkNanos (Object blocker, long deadline)
  5. LockSupport#parkUntil (long deadline)

RUNNABLE(運行狀態)與 TERMINATED(終止狀態)

線程一旦執行結束或者線程執行過程發生異常且未正常捕獲處理,狀態都將會自動變成 TERMINATED

Java 線程 6 種狀態看起來挺複雜的,但其實上面 BLOCKEDWATTINGTIMED_WAITING,都會使線程處於休眠狀態,因此咱們將這三類都歸類爲休眠狀態。這麼分類的話,Java 線程生命週期就能夠簡化爲下圖:

java線程狀態2.png

通用操做系統線程狀態

上面講完 Java 系統的線程狀態,咱們來看下通用操做系統的線程狀態。操做系統線程狀態能夠分爲初始狀態,可運行狀態,運行狀態,休眠狀態以及終止狀態,以下圖:

操做系統線程狀態1.png

這 5 中狀態詳細狀況以下:

  1. 初始狀態,這時候線程剛被建立,還不能分配 CPU 。
  2. 可運行狀態,線程等待系統分配 CPU ,從而執行任務。
  3. 運行狀態,操做系統將 CPU 分配給線程,線程執行任務。
  4. 休眠狀態,運行狀態下的線程若是調用阻塞 API,如阻塞方式讀取文件, 線程狀態就將變成休眠狀態。這種狀況下,線程將會讓出 CPU 使用權。休眠結束,線程狀態將會先變成可運行狀態。
  5. 線程執行結束或者執行過程發生異常將會使線程進入終止狀態,這個狀態下線程使命已經結束。

對比二者線程狀態

比較 Java 線程與操做系統線程,能夠發現 Java 線程狀態沒有可運行狀態。也就是說 Java 線程 RUNNABLE 狀態包括了操做系統的可運行狀態與運行狀態。一個處於 RUNNABLE 狀態 Java 線程,在操做系統層面狀態可能爲可運行狀態,正在等待系統分配 CPU 使用權。

另外 Java 線程細分了操做系統休眠狀態,分紅了 BLOCKEDWATTINGTIMED_WAITING 三種。

當線程調用阻塞式 API,線程進入休眠狀態,這裏指的是操做系統層面的。從 JVM 層面,Java 線程狀態依然處於 RUNNABLE 狀態。JVM 並不關心操做系統線程實際狀態。從 JVM 看來等待 CPU 使用權(操做系統線程狀態爲可運行狀態)與等待 I/O (操做系統線程狀態處於休眠狀態)沒有區別,都是在等待某種資源,因此都納入 RUNNABLE 狀態。

其餘 Java 線程狀態與操做線程狀態相似。

面試官:都說阻塞 I/O 模型將會使線程休眠,爲何 Java 線程狀態倒是 RUNNABLE?
其餘平臺.png

相關文章
相關標籤/搜索