一、線程的狀態簡介
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
- 線程進入
Synchronized
方法或語句塊; - 線程調用
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
(等待隊列),通過 Synchronized
和 wait/notify
進行轉換:
- 線程通過
Synchronized
獲取monitor
鎖,多線程時,未獲取到鎖的線程進入Entry Set
等待; - 已獲取
monitor
鎖的線程,通過調用Object#wait
方法,進入Wait Set
等待區,並釋放monitor
鎖; - 其他線程通過
Object#notify, Object#notifyAll
方式喚醒Wait Set
區內的線程,競爭 對象Monitor
鎖。
圖示如下:
注意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
運行中:
等待資源:
WAITING
調用 Object.wait()
:
調用 LockSupport#park()
:
TIMED_WAITING
調用 Object#wait(long)
:
調用 Thread#sleep(long)
:
調用 LockSupport#parkNanos(long)
:
參考資料
java.lang.Thread.State
Java Language Specification
Java線程同步機制
Monitors – The Basic Idea of Java Synchronization
Java線程的6種狀態及切換(透徹講解)