多線程開發基礎總結(一)—— java中線程的狀態以及狀態轉換

java中線程的狀態以及狀態轉換(詳解)

前言

在進行多線程開發之前,一定要先了解的就是線程的狀態的各個狀態的情況以及狀態之間的轉化。網上關於線程狀態的說法大概有三個版本,實際上都沒有太大的問題,因爲有的是從操作系統上去對線程狀態進行劃分,而有的是根據java源碼來劃分,所以會有一種情況就是你在這邊看到線程有這幾種狀態,但是在另一邊,則變成了另外的解釋。並且,中間也有很多寫的並不詳細,也有一部分雖然沒有大問題,但是中間混淆了概念。那麼,這種情況下,看源碼是最好的選擇

java關於線程狀態的源碼

java中用了一個枚舉類來表示線程的狀態,分別是NEW(新建)、RUNNABLE(可運行)、BLOCKED(阻塞)、TIMED_WAITING(定時等待)、WAITING(等待)、TERMINATED(終止、結束)。

附上源碼:

 public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

初始狀態(New)

用了new語句創建後但並未啓動的線程就處於初始的狀態,此時,和java其他所有的對象調用new語句一樣,只是分配了內存,並對成員變量進行初始化,但是此時的線程對象並沒有展示線程的任何動態特性;而從源碼註釋上也知道,官方對這個狀態的定義是:尚未啓動的線程的線程狀態,這個狀態基本沒什麼異議

就緒狀態(操作系統的Ready狀態)

線程處於初始狀態,然後調用了線程的start方法,根據需要進行資源競爭,比如進入鎖池中,參與鎖的競爭;而在獲得資源之後,並不能馬上進入執行的狀態,因爲還需要時間片的分配;只有被線程調度器選中,分配了時間片,才能進入運行狀態;而在一個線程獲得鎖之後,獲得時間片之前,稱爲就緒狀態(操作系統的Ready狀態);但是,從java源碼中可以看到並沒有單獨的就緒狀態,需要注意的是,調用start方法之後默認的線程就進入就緒狀態,JVM會爲該線程創建方法並且調用堆棧程序計數器,往往start之後的線程並不會立即執行,但是其實通過對線程進行操作讓它立刻執行,比如設置其睡眠一毫秒,這裏關於start方法等待的內容稍後再說;此處還需要謹記的一點是,只能對處於新建狀態的線程調用start方法,否則會報錯!

運行狀態(操作系統中的Running狀態)

當線程處於Ready狀態之後,此時所有處於此狀態的線程存在於一個線程池中,等待CPU調度選中,分配時間片;當線程分配到時間片之後,線程佔用cpu,執行程序代碼,此時,線程便處於一個運行的狀態,這就是操作系統中的Running狀態。但是,java源碼中並不存在單獨的Running運行狀態。

RUNNABLE狀態(可運行)

其實很多人也會把操作系統的Ready狀態叫做可運行狀態,其實這個叫法也沒問題。上面講到,java中並沒有單獨的就緒狀態和運行狀態,取而代之的是一個叫做RUNNABLE的狀態,在Java源碼的註釋中可以看到,官方對於RUNNABLE的定義是 :A thread in the runnable state is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor.
意思是 處於runnable狀態的線程在JVM中執行,但也可能處於一個等待操作系統其他資源例如處理器(時間片)的狀態。所以其實這裏的可運行狀態包含了操作系統中的Ready狀態和Running狀態 聯繫上面的我們可以知道的一點就是,在java中,線程新建之後調用start方法,便進入了所謂的Runnable狀態。

BLOCKED(阻塞狀態)

BLOCKED我把它直譯過來就是阻塞狀態,其實這樣並不嚴謹,操作系統上對於阻塞狀態的定義是指線程因爲某些原因放棄CPU,暫時停止運行,而被阻塞的線程也會在合適的機會重新進入就緒狀態。在我看來,完整看完官方對於BLOCKED、WAITING和TIMED_WAITING,我感覺java中更像是將阻塞狀態細分成了這三種(個人理解)。

這是官方對於阻塞狀態的解釋:A thread in the blocked state is waiting for a monitor lock
to enter a synchronized block/method or
reenter a synchronized block/method after calling
{@link Object#wait() Object.wait}
.

大概意思是說等待獲取一個對象鎖(同步鎖/也叫同步監視器)來進入一個同步塊/同步方法,或者 調用了wait方法之後釋放了本身所持有的鎖然後想重新進入一個同步塊/方法中。而操作系統上對於阻塞狀態的定義是指線程因爲某些原因放棄CPU,暫時停止運行,而被阻塞的線程也會在合適的機會重新進入就緒狀態。當線程處於阻塞狀態時,Java虛擬機不會給線程分配CPU。

這部分網上有人講BLOCKED是鎖池狀態,把BLOCKED叫鎖池其實有誤導的成分,看了他們的關係轉換圖,其實有些是挺有問題的,很多人寫着寫着就和JVM的一些概念混在一起了,我個人覺得,從源碼上來看,處於BLOCKED狀態的核心是在等待接受一個監視器(排它鎖)讓它來進入同步塊,從這來看倒是確實很像lock pool(這纔是鎖池)的概念,但是,java並不存在鎖池狀態的這樣一個概念,鎖池只是存放那些處於等待獲取被其他線程佔用了的對象的同步鎖的線程的地方(有點拗口,好好斷句會理解的吧),羣毆自己理解就是存放那些處於BLOCKED狀態的線程的地方;總之,從源碼上來看,BLOCKED關鍵點在於 waiting for a monitor lock to enter a synchronized block

WAITING(無限期等待)

直接看源碼:A thread is in the waiting state due to calling one of the following methods:
{@link Object#wait() Object.wait} with no timeout
{@link #join() Thread.join} with no timeout
{@link LockSupport#park() LockSupport.park}
A thread in the waiting state is waiting for another thread to
perform a particular action

大概意思是一個線程進行了以下三種操作中的一種就屬於無限期等待狀態,這三種操作分別是:

  1. 調用沒有設置 Timeout 參數的 Object.wait() 方法
  2. 調用沒有設置 Timeout 參數的 Thread.join() 方法
  3. 調用LockSupport.park() 方法;

並且,處於WAITING狀態的線程會等待另一個線程進行特定操作纔會退出WAITING狀態。比如:

如果因爲調用了wait方法造成了處於WAITING狀態的話,那麼等待另一個線程調用Object.notify() / Object.notifyAll()後便可以退出這種狀態

如果是因爲調用join方法,那麼等待被調用的線程執行完畢即可退出

調用LockSupport.park()造成的則等待其他線程調用LockSupport.unpark(Thread)進行喚醒。

需要注意的是,處於無限期等待狀態的線程,需要等待其它線程顯式地喚醒,否則不會被分配 CPU 時間片。

TIMED_WAITING(定時等待)

從源碼可以知道,處於定時等待狀態的原因可能是:

  1. 因爲調用了Thread.sleep() 方法
  2. 可能是調用設置了 Timeout 參數的 Object.wait() 方法
  3. 可能是調用設置了 Timeout 參數的Thread.join() 方法
  4. 還有就是可能調用了LockSupport.parkNanos() 方法
  5. 調用了LockSupport.parkUntil() 方法

和WAITING狀態不一樣的是,限時等待無需等待其它線程顯式地喚醒,在一定時間之後會被系統自動喚醒。

而退出定時等待狀態的方法與上面對應的分別是:

  1. 時間結束
  2. 時間結束或者調用 Object.notify() / Object.notifyAll()
  3. 時間結束 或者 被調用的線程執行完畢
  4. 調用LockSupport.unpark(Thread)
  5. 調用LockSupport.unpark(Thread)

上面也講到,我個人理解的是JAVA把阻塞狀態細化成了以上三種,而把這三種總結起來大概可以把造成阻塞狀態的比較常見的原因分爲:

  1. 線程試圖調用一個同步監視器,但是這個監視器被其他線程持有
  2. 線程調用了阻塞IO方法,在該方法返回之前,這個線程都處於阻塞狀態
  3. 線程調用sleep方法,放棄佔有的處理器資源(需注意的點是和調用wait方法有區別,sleep是不會釋放持有的鎖,而wait會)
  4. 線程等待某種通知(notify/notifyAll)這裏也有要注意的點,就是應用層線程實例儘量不要使用notifyall、notify和wait方法,這兩個是object方法,他們是系統調用的,如果你在線程實例上使用這幾個方法,可能不一定會按照你想要的結果來工作,這些之後再來詳細講
  5. 程序調用了線程的suspend()方法(線程掛起)這個也有需要注意的地方,suspeng()方法在源碼中可以看到已經是過時方法,意思是不推薦使用,原因suspend()不會釋放鎖,如果在resume方法之前發生加鎖行爲,那麼就會造成死鎖的情況,出現線程被凍結的情況;這個也以後再細講。
  6. 調用了join()方法,這個其實不應該單獨成爲一個原因,因爲實質上調用join方法會判斷當前執行的線程是否執行結束,執行線程如果結束,那麼這個線程會調用notifyall方法,然後結束等待;實際上屬於第4類,但是上面沒講到,就還是把它單獨拿出來講一下

上面這些是我對java線程阻塞的理解的總結,而且綜合起來,會發現這個也比較符合操作系統中對於線程阻塞狀態的定義。

TERMINATED(終止、結束、死亡)

對於這個,還是比較統一的,我也總結了線程結束的幾種情況:

  1. 是線程結束任務之後正常結束,比如run()方法和call()方法執行正常完成之後正常死亡
  2. 拋出了一個未捕獲的異常或者error而結束
  3. 直接調用了stop()方法,但是這個從源碼中也可以看到這個不推薦使用,因爲它很暴力,線程可能當時正在執行,此時直接調用stop方法,線程會釋放所有的鎖,可能會出現髒數據,破環數據一致性,並且很難發現(不會報錯)

結束語

其實我之前並沒打算單獨寫一篇這麼長的文章來單獨寫這個,而且最近寫了幾篇學習總結,還沒整理好,本來是打算先寫好基礎的總結,順便寫一下多線程開發中很多基礎方法的介紹,對於線程狀態的我是打算copy一下,然後一筆帶過就夠了,但是再進行學習的過程中,發現很多人可能和我一樣的想法,並沒深入研究或者研究了沒詳細解釋也是直接一筆帶過了,並且很多的還存在一些誤解(我認爲的),所以我還是決定花點時間來詳細寫一下這個,還差一張狀態轉換圖,我之後再補上吧,當然,如果你覺得我所講的有問題,歡迎評論糾正我,共同學習,一起進步!

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