Java Concurrency #4: 執行緒的生命週期 — Thread LifeCycle

Charlie Lee
Bucketing
Published in
4 min readJul 19, 2020
Photo by Ravi Roshan on Unsplash

文章架構

  • Thread生命週期
  • sleep / wait
  • wait / notfiy 範例

Thread生命週期

OS Thread生命週期

作業系統層面的Thread會經歷

  1. Born — 被創造
  2. Ready — 準備就緒等待執行
  3. Running — 執行中
  4. Waiting — 等待被呼叫
  5. Sleeping — 在一定秒數後再次醒來執行
  6. Dead — 結束
  7. Blocked — 在執行被Lock資源時,等待資源被釋放

JAVA Thread生命週期

Java Thread與OS Thread差別在於,Java使用的Thread是JVM提供的虛擬Thread(貼近原生Thread),JVM在編譯程式後會對Thread code進行優話

  1. new — 當開發者new Thread
  2. runnable — 開發者start Thread時,因為此時的Thread對於OS不一定是在執行狀態,所以叫做runnable而不是running
  3. blocked — 當遇到lock需要排隊時
  4. waiting — 呼叫wait()時
  5. time waiting — 當呼叫sleep(ms) / wait(ms)給於時間
  6. terminated — 執行結束

sleep與wait

共同

  • 當Thread進入sleep或是wait的時候CPU是不會被Thread占用的
  • 也代表可以使用sleep or wait降低輪詢(poll)程式碼被跑到的機率

差異

  • sleep不可被notify / notifyAll叫醒,wait可以
  • sleep一定需要給於秒數 而wait不用 (因為sleep不可被叫醒)

使用時機,當Thread需要和其它Thread連動時,需要使用wait搭配notify,例如

  • 訂閱執行緒是否完成 / 等待的Thread wait等待工作的Thread notify

而sleep,則是此Thread的運作不可被打擾時使用,例如:

  • 定時器/批次作業功能

等待/通知 (wait/notify) 範例

在這個範例,使用wait/notify優化死鎖篇提到到過的Allocator,下面是原本的Allocator程式碼。當Transfer拿不到鎖的時候,會不斷的輪詢(poll)Allocator物件,檢查lock是否被釋放,但輪詢(poll)是非常消耗CPU時間的(CPU需要將資源消耗在這個檢查的行為上)。

如何優化這段程式碼呢? — 最大可能的消除輪詢(poll)

while(拿鎖失敗) {
wait() //拿不到
}

將Allocator被輪詢的程式碼片段轉換成wait,並且在Allocator釋放鎖的時候,對所以有等待中的Thread做通知。範例如下

新Allocator與舊Allocator最大的差異在於,判斷鎖已被拿走後一個是用while與wait等待鎖的釋放,另一個是直接回傳false讓外面的程式碼自己想辦法。而使用wait之後並不會讓while持續的輪詢,直到有鎖被釋放(free)的時候才會再檢查一次直到自己拿到鎖

總結

本篇文章介紹了執行緒的生命週期,包含OS版本與Java版本,其中最關鍵的生命週期是waiting,waiting與blocking的差異在於一個是開發者設計讓Thread主動待命,一個是Thread遇到需要同步區排隊等待lock的釋放。

在文章最後,使用wait/notify優化了上一篇死鎖的Allocator物件,減少輪詢優化了CPU使用效率。

--

--