synchronized的底層實現
引言
在單機環境中的併發程式設計中,需要用鎖來保證資料的安全性。我們經常會用到synchronized,那麼JVM中是如何實現synchronized的呢,在這篇文章中,我會從鎖分類和鎖膨脹(鎖升級)的角度,會來探析一二。
為什麼要有鎖物件
Object lockObj = new Object();
synchronized(lockObj){
//TODO
}
在訪問某塊程式碼或者變數時,為了防止執行緒安全問題,我們需要先獲取鎖物件,通過鎖的互斥來保證執行緒的安全訪問。在上面這塊程式碼中,lockObj就是鎖物件。
synchronized底層實現的鎖分類
- 偏向鎖
- 輕量級鎖
- 重量級鎖
在這3種分類中,偏向鎖和輕量級鎖是在邏輯層面做的一些處理,並非真正的鎖,只有重量級鎖,才是真正的鎖(作業系統層面的鎖)。
為什麼要對鎖分類
假設現在有A和B兩個執行緒,要訪問共有變數。一般來說有如下3種情況:
- 每次只有執行緒A或者執行緒B單獨使用。
- 執行緒A和執行緒B交替使用。
- 執行緒A和執行緒B,同時使用。
眾所周知,獲取鎖是比較消耗效能的。所以在Java中,synchronized提供了幾種鎖的實現來優化。
對於這3種情況,前兩種可以在邏輯層面做一些處理,避免每次獲取鎖(作業系統鎖),帶來的效能開銷。對於1,可以使用偏向鎖。對於2,可以使用輕量級鎖。
偏向鎖
偏向鎖會保證物件被執行緒安全的訪問。
鎖物件
被synchronized鎖保護的,稱作鎖物件。鎖物件中包含了鎖物件頭,由執行緒id、Epoch、分代年齡、是否偏向鎖標記、鎖標記組成。
執行緒id:每次獲取鎖物件時,會先檢查執行緒id是否與當前執行緒一致,如果執行緒id是空,則通過CAS設定物件頭中的執行緒id。
Epoch:本質是時間戳。使用Epoch通過CAS來保證設定執行緒id的安全性。
執行原理
在獲取鎖物件時,首先會檢查鎖物件頭中的執行緒id是否與當前執行緒一致。
- 如果執行緒id是空,則通過CAS設定物件頭中的執行緒id,並更新Epoch。
- 如果執行緒id與當前執行緒一致,則可以安全訪問。
- 如果執行緒id與當前執行緒不一致,則需要鎖膨脹。(升級為輕量級鎖)
輕量級鎖
在偏向鎖獲取不到鎖物件時,會通過自旋來不斷的嘗試獲取鎖,這就稱為輕量級鎖。
重量級鎖
在通過一定的自旋次數後,如果還獲取不到鎖,就會升級為重量級鎖,所有獲取不到鎖物件的執行緒都會被阻塞(Blocked狀態)。
重量級鎖會使用Monitor獲取作業系統的MutexLock(互斥鎖)
什麼時候切換鎖型別
在預設情況下,會先嚐試使用偏向鎖,如果獲取不到,則升級為輕量級鎖,輕量級鎖在一定的自旋次數後,會升級為重量級鎖。獲取鎖是按:偏向鎖->輕量級鎖->重量級鎖,依次升級,且無法降級。
只有噹噹前鎖無法獲取到鎖物件時,才會升級。