1. 程式人生 > 其它 >synchronized的底層實現

synchronized的底層實現

引言

在單機環境中的併發程式設計中,需要用鎖來保證資料的安全性。我們經常會用到synchronized,那麼JVM中是如何實現synchronized的呢,在這篇文章中,我會從鎖分類和鎖膨脹(鎖升級)的角度,會來探析一二。

為什麼要有鎖物件

Object lockObj = new Object();
synchronized(lockObj){
    //TODO
}

在訪問某塊程式碼或者變數時,為了防止執行緒安全問題,我們需要先獲取鎖物件,通過鎖的互斥來保證執行緒的安全訪問。在上面這塊程式碼中,lockObj就是鎖物件。

synchronized底層實現的鎖分類

  1. 偏向鎖
  2. 輕量級鎖
  3. 重量級鎖
    在這3種分類中,偏向鎖和輕量級鎖是在邏輯層面做的一些處理,並非真正的鎖,只有重量級鎖,才是真正的鎖(作業系統層面的鎖)。

為什麼要對鎖分類

假設現在有A和B兩個執行緒,要訪問共有變數。一般來說有如下3種情況:

  1. 每次只有執行緒A或者執行緒B單獨使用。
  2. 執行緒A和執行緒B交替使用。
  3. 執行緒A和執行緒B,同時使用。

眾所周知,獲取鎖是比較消耗效能的。所以在Java中,synchronized提供了幾種鎖的實現來優化。
對於這3種情況,前兩種可以在邏輯層面做一些處理,避免每次獲取鎖(作業系統鎖),帶來的效能開銷。對於1,可以使用偏向鎖。對於2,可以使用輕量級鎖。

偏向鎖

偏向鎖會保證物件被執行緒安全的訪問。

鎖物件

被synchronized鎖保護的,稱作鎖物件。鎖物件中包含了鎖物件頭,由執行緒idEpoch、分代年齡、是否偏向鎖標記、鎖標記組成。

執行緒id:每次獲取鎖物件時,會先檢查執行緒id是否與當前執行緒一致,如果執行緒id是空,則通過CAS設定物件頭中的執行緒id。
Epoch:本質是時間戳。使用Epoch通過CAS來保證設定執行緒id的安全性。

執行原理

在獲取鎖物件時,首先會檢查鎖物件頭中的執行緒id是否與當前執行緒一致。

  1. 如果執行緒id是空,則通過CAS設定物件頭中的執行緒id,並更新Epoch。
  2. 如果執行緒id與當前執行緒一致,則可以安全訪問。
  3. 如果執行緒id與當前執行緒不一致,則需要鎖膨脹。(升級為輕量級鎖)

輕量級鎖

在偏向鎖獲取不到鎖物件時,會通過自旋來不斷的嘗試獲取鎖,這就稱為輕量級鎖。

重量級鎖

在通過一定的自旋次數後,如果還獲取不到鎖,就會升級為重量級鎖,所有獲取不到鎖物件的執行緒都會被阻塞(Blocked狀態)。
重量級鎖會使用Monitor獲取作業系統的MutexLock(互斥鎖)

什麼時候切換鎖型別

在預設情況下,會先嚐試使用偏向鎖,如果獲取不到,則升級為輕量級鎖,輕量級鎖在一定的自旋次數後,會升級為重量級鎖。獲取鎖是按:偏向鎖->輕量級鎖->重量級鎖,依次升級,且無法降級。
只有噹噹前鎖無法獲取到鎖物件時,才會升級。