iT邦幫忙

2026 iThome 鐵人賽

0
Build on Google AI

把Google AI當夥伴打造個人高效Coding工作流系列 第 17 篇

第十七天Thread生命週期六大狀態切換、synchronized底層Monitor機制與死鎖預防

  • 分享至 

  • xImage
  •  

一、 執行緒(Thread)的物理代價與本質
排程實體:在現代 HotSpot JVM 中,Java 的 Thread 採用 1:1 映射至作業系統原生核心執行緒(Kernel-Level Thread, KLT)。

資源開銷:

記憶體:每個 Java 執行緒預設分配獨立的呼叫棧空間(Thread Stack,預設約 1MB,由 -Xss 控制)。建立 1000 個執行緒即佔用約 1GB 記憶體。

CPU 成本:執行緒切換涉及作業系統核心態與使用者態(Kernel Mode vs User Mode)切換,以及 CPU 暫存器、程式計數器(PC Register)的上下文切換(Context Switch)開銷。


二、 JVM 定義的 Thread 六大生命週期狀態(Thread.State)
作業系統通常將行程分為五態(新建、就緒、運行、阻塞、終止),而 Java Thread.State 列舉定義了嚴格的六種狀態:

  1. NEW(新建):剛 new Thread() 出來,尚未呼叫 start()。
  2. RUNNABLE(可運行):包含作業系統層面的 Ready(就緒) 與 Running(運行中)。代表執行緒正在執行,或正在等待 CPU 分配時間片(Time Slice)。
  3. BLOCKED(阻塞):唯一專指「等待進入 synchronized 臨界區」的狀態。當執行緒試圖獲取被其他執行緒持有的 Monitor 監視器鎖時,會被掛起並進入 BLOCKED。
  4. WAITING(無限等待):主動釋放 CPU 與鎖資源,等待其他執行緒發出特定指令(通知或中斷)。
    觸發方法:Object.wait()、Thread.join()、LockSupport.park()
  5. TIMED_WAITING(計時等待):帶有超時機制的等待,時間一到或收到通知即自動返回。
    觸發方法:Thread.sleep(ms)、Object.wait(ms)、Thread.join(ms)、LockSupport.parkNanos()。
  6. TERMINATED(終止):run() 方法正常執行完畢,或因未捕獲的例外導致異常中斷,生命週期終結。已終止的執行緒嚴禁再次調用 start()(會拋出 IllegalThreadStateException)。

三、 synchronized 底層核心:ObjectMonitor 監視器機制synchronized 關鍵字並非純語法糖,它依賴 JVM 底層的 ObjectMonitor(物件監視器) 進行併發控制。

  1. 物件頭部(Object Header)與 Mark Word每個 Java 物件在 Heap 的記憶體佈局都包含一個 Mark Word(64-bit 系統下佔 8 Bytes):存放物件的 hashCode、GC 分代年齡(Age)。存放 鎖狀態標記(Lock Flag):無鎖 $\rightarrow$ 偏向鎖(已在 JDK 15 棄用) $\rightarrow$ 輕量級鎖 $\rightarrow$ 重量級鎖。當升級為重量級鎖時,Mark Word 會改為存放一個指向作業系統層級 ObjectMonitor 的記憶體指針。
  2. 字節碼指令:monitorenter 與 monitorexit修飾程式碼區塊時,編譯後的字節碼會明確插入 1 個 monitorenter 與 2 個 monitorexit。為什麼有 2 個 exit 指令?一個對應正常執行結束的退出,另一個對應發生例外時的異常退出,JVM 保證即便臨界區拋出任何例外,鎖一定會被自動釋放,避免死鎖。
  3. ObjectMonitor 的內部三大結構
  • 爭搶流程:執行緒進入 _EntryList 搶鎖;成功時 _owner 指向該執行緒,_recursions 設為 1。
  • 可重入性(Reentrancy):若持有鎖的執行緒再次進入同一個鎖保護的代碼塊,_owner 仍為自己,_recursions++;退出時 _recursions--,減至 0 時才真正釋放鎖。
  • 釋放與喚醒:持有鎖的執行緒呼叫 wait(),會清空 _owner 並將自己移入 _WaitSet;其他執行緒呼叫 notify() 時,會將 _WaitSet 中的執行緒移回 _EntryList 重新參與競爭。

四、 死鎖(Deadlock)的四個必要條件與破局防禦當兩個或多個執行緒彼此互相持有對方需要的鎖、且都在等待對方釋放時,系統便陷入永久停滯。
1. 死鎖發生的四大必要條件(缺一不可):互斥條件(Mutual Exclusion):資源同一時間只能被一個執行緒佔有(鎖的本質)。持有並等待(Hold and Wait):執行緒已持有至少一個資源,但又發起請求爭奪已被其他執行緒佔有的新資源。不可剝奪(No Preemption):已獲取的資源在未主動釋放前,外部無法強行剝奪。循環等待(Circular Wait):存在一條執行緒與鎖的封閉環形鏈條(Thread A 拿著 Lock 1 等 Lock 2,Thread B 拿著 Lock 2 等 Lock 1)。
2. 架構預防死鎖的工程守則:破除循環等待(全域鎖排序,Lock Ordering):黃金法則:所有執行緒在請求多個鎖時,必須按照嚴格相同的順序獲取鎖。例如轉帳情境:比較兩個帳戶的 ID 大小,永遠先鎖 ID 較小的帳戶,再鎖 ID 較大的帳戶,徹底消除環形依賴。破除不可剝奪(改用顯式鎖 ReentrantLock):synchronized 無法被中斷且無法設定超時。改用 JUC 的 ReentrantLock.tryLock(timeout, unit):若在指定時間內拿不到鎖,主動放棄已持有的資源並退讓重試,破除僵局。排查死鎖工具:生產環境排查:使用 JDK 內建命令列工具 jstack ,工具會自動掃描執行緒棧並輸出 Found one Java-level deadlock 與對應代碼行號。


上一篇
第十六天Collections工具類防禦陷阱、Arrays.asLis隱患與Java 9 List.of不可變集合
下一篇
第十八天Java記憶體模型、volatile可見性與指令重排、CAS與Atomic原子類
系列文
把Google AI當夥伴打造個人高效Coding工作流 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言