Java Concurrency #4: 執行緒的生命週期 — Thread LifeCycle
文章架構
- Thread生命週期
- sleep / wait
- wait / notfiy 範例
Thread生命週期
OS Thread生命週期
作業系統層面的Thread會經歷
- Born — 被創造
- Ready — 準備就緒等待執行
- Running — 執行中
- Waiting — 等待被呼叫
- Sleeping — 在一定秒數後再次醒來執行
- Dead — 結束
- Blocked — 在執行被Lock資源時,等待資源被釋放
JAVA Thread生命週期
Java Thread與OS Thread差別在於,Java使用的Thread是JVM提供的虛擬Thread(貼近原生Thread),JVM在編譯程式後會對Thread code進行優話
- new — 當開發者new Thread
- runnable — 開發者start Thread時,因為此時的Thread對於OS不一定是在執行狀態,所以叫做runnable而不是running
- blocked — 當遇到lock需要排隊時
- waiting — 呼叫wait()時
- time waiting — 當呼叫sleep(ms) / wait(ms)給於時間
- 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使用效率。