[Java] 線程的狀態及轉換

一、線程的狀態簡介

JDK 的線程狀態枚舉類 java.lang.Thread.State ,定義了線程的6種狀態:

  • NEW: 新建線程,尚未調用 thread.start()
  • RUNNABLE: 可在 JVM 中執行,需等待CPU資源
  • BLOCKED: 調用了 Synchronized 方法或語句塊,等待對象鎖資源
  • WAITING: 調用了 Object#wait(), Thread#join(), LockSupport#park() 等方法中的一個,等待其他線程執行特定的動作 (notify 或 線程終止)
  • TIMED_WAITING: 調用了 Thread#sleep(long), Object#wait(long), Thread#join(long), LockSupport#parkNanos, LockSupport#parkUntil 等方法中的一個
  • TERMINATED: 線程執行完畢

二、線程的狀態轉換

線程狀態轉換

注:上面列出的爲線程的正常狀態流轉,可調用 Thread#interrupt() 中斷。

RUNNABLE

RUNNABLE 狀態表示該線程已就緒,可運行,但不一定能立刻執行,還需等待CPU 調度 。

BLOCKED

  1. 線程進入 Synchronized 方法或語句塊;
  2. 線程調用 Object#wait 方法被 Object#notify/notifyAll 喚醒後,變爲 RUNNABLE 狀態,並重新競爭 monitor 鎖;

注:線程需要先競爭 monitor 鎖成功,再等待 CPU 調度。

WAITING

線程調用 Object#wait(), Thread#join(), LockSupport#park() 等方法中的一個,進入 WAITING 狀態。需要其他線程調用 Object#notity() 或 Object#notifyAll() 方法喚醒線程,否則一直等待。

TIMED_WAITING

線程調用 Thread#sleep(long), Object#wait(long), Thread#join(long), LockSupport#parkNanos, LockSupport#parkUntil 等方法中的一個,進入 TIMED_WAITING 狀態。其他線程調用 Object#notity(), Object#notifyAll() 或超時時間到達,喚醒線程。

三、monitor 和 wait set

Java Language Specification 的描述中,Java 每個對象都有一個關聯的 monitor(監視器) 和 wait set(等待隊列),通過 Synchronizedwait/notify 進行轉換:

  1. 線程通過 Synchronized 獲取 monitor 鎖,多線程時,未獲取到鎖的線程進入 Entry Set 等待;
  2. 已獲取 monitor 鎖的線程,通過調用 Object#wait 方法,進入 Wait Set 等待區,並釋放 monitor 鎖;
  3. 其他線程通過 Object#notify, Object#notifyAll 方式喚醒 Wait Set 區內的線程,競爭 對象 Monitor 鎖。

圖示如下:

monitor and wait set

注意1:在調用 Object#wait, Object#notify, Object#notifyAll 等方法之前,必須先獲得該對象的 monitor

注意2: 通過調用 Synchronized 獲取對象的 monitor 鎖的方式有:

class Ex {
    private Object L = new Object();

    // 鎖對象是Ex.class
    public static synchronized A() { ... }
    
    // 鎖對象是當前對象(this)
    public synchronized A() { ... }
    
    public A() {
        // 鎖對象是L
        synchronized(L) {
            ...
        }
    }
}

四、方法說明

Thread#sleep(long)

調用後進入 TIMED_WAITING 狀態,使當前線程休眠一定時間,不會釋放對象鎖,時間到了,則線程進入 RUNNABLE 狀態。

Object#wait()/Object#wait(long)

調用後線程狀態變爲 WAITING/TIMED_WAITING,進入等待隊列 (wait set),並釋放監視器鎖

注:當前線程必須先獲得監視器鎖,否則拋 IllegalMonitorStateException 異常。

Thread#join()

等待當前線程消亡,同步方法。內部通過判斷線程 isAlive() 狀態,循環調用 Object#wait(long)

Object#notify()/notifyAll()

notify: 喚醒一個等待隊列中的線程,如果有多個線程,則由 JVM 任意挑選一個。
notifyAll: 喚醒所有等待隊列中的線程。

其他線程調用 Object#notify()/notifyAll() 後,當前線程變爲 RUNNABLE,並重新競爭 monitor 鎖,競爭成功狀態變爲 RUNNABLE,競爭失敗變爲 BLOCKED

調用 notify/notifyAll 後,調用線程並不一定立即釋放 monitor 鎖,需等待對應邏輯執行完,即線程爲 terminated 或被中斷,纔會釋放。

注:當前線程必須先獲得監視器鎖,否則拋 IllegalMonitorStateException 異常。

Thread#yield()

提示 CPU 可以釋放當前線程佔用的 CPU 資源,防止 CPU 過度使用,但 CPU 可以忽略這個提示。

讓出 CPU 資源後,當前線程變爲 RUNNABLE 狀態,繼續和其他線程競爭 CPU 資源。

Thread#interrupt()

可中斷正在運行的線程。需要注意的點有:

  • 如果中斷的不是正在運行的線程,會調用 java.lang.Thread#checkAccess 方法,可能拋出 java.lang.SecurityException 異常
  • 如果線程阻塞在 wait, join, sleep 等方法時,中斷狀態會被清除,並拋出 java.lang.InterruptedException 異常
  • 如果線程阻塞在使用 java.nio.channels.InterruptibleChannel 的 I/O 操作,會關閉管道,設置中斷狀態,拋出 java.nio.channels.ClosedByInterruptException 異常
  • 如果線程阻塞在 java.nio.channels.Selector設置中斷狀態,並立即返回,和調用 Selector#wakeup() 方法一樣
  • 如果不是以上列出的情形,則設置中斷狀態
  • 不會對已終結的線程造成影響

五、如何查看線程狀態

使用 jstack <pid>命令,可以查看進程的線程信息。

RUNNABLE

運行中:

runnable

等待資源:

waiting

WAITING

調用 Object.wait():

Object#wait()

調用 LockSupport#park():

LockSupport#park()

TIMED_WAITING

調用 Object#wait(long):

Object#wait(long)

調用 Thread#sleep(long):

Thread#sleep(long)

調用 LockSupport#parkNanos(long):

LockSupport#parkNanos

參考資料

java.lang.Thread.State
Java Language Specification
Java線程同步機制
Monitors – The Basic Idea of Java Synchronization
Java線程的6種狀態及切換(透徹講解)

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