synchronized鎖升級程序
接觸過執行緒安全的同學想必都使用過synchronized這個關鍵字,在java同步代碼塊中,synchronized的使用方式無非有兩個:
1通過對一個物件進行加鎖來實作同步;
synchronized(lockObject){
...
}
2對一個方法進行加鎖實作同步
public synchornized void test(){
...
}
無論是對一個物件進行加鎖還是對一個方法進行加鎖,實際上,都是對物件進行加鎖,
對于方式2,實際上虛擬機會根據synchronized修飾的是實體方法還是類方法,去取對應的實體物件或者Class物件來進行加鎖,
鎖是加在物件上的,那么一個執行緒是如何知道這個物件被加了鎖呢?又是如何知道它加的是什么型別的鎖呢?
基于這些問題,下面我講一步一步講解synchronized是如何被優化的,是如何從偏向鎖到重量級鎖的,
鎖物件
鎖實際上是加在物件上的,那么被加了鎖的物件我們稱之為鎖物件,在java中,任何一個物件都能成為鎖物件,
為了更好著理解虛擬機是如何知道這個物件就是一個鎖物件的,下面簡單介紹一下java中一個物件的結構,
java物件在記憶體中的存盤結構主要有一下三個部分:
1.物件頭(Markword(4位元組),Class物件指標(4位元組))
2.實體資料
3.對齊填充資料(按8位元組對齊)
這里強調一下,物件頭里的資料主要是一些運行時的資料,
其簡單的結構如下
| 長度 | 內容 | 說明 |
|---|---|---|
| 32/64bit | Mark Word | hashCode GC分代年齡,鎖資訊 |
| 32/64bit | Class pointer | 指向物件型別資料的指標 |
| 32/64bit | Array Length | 陣列的長度(當物件為陣列時) |
從表格可以看到,物件中關于鎖對資訊鎖存在Markword里的,
LockObject lockObject = new LockObject();//隨便創建一個物件
synchronized(lockObject){
...
}
當我們創建一個物件LockObject時,該物件的部分Markword關鍵資料如下:
| bit fields | 是否偏向鎖 | 鎖標志位 |
|---|---|---|
| hash | 0 | 01 |
偏向鎖的標志位是“01”,狀態是“0”,表示該物件還沒有被加上偏向鎖,(“1”是表示被加上偏向鎖),該物件被創建出來的那一刻,就有了偏向鎖的標志位,這也說明了所有物件都是可偏向的,但所有物件的狀態都為“0”,也同時說明所有被創建的物件的偏向鎖并沒有生效
偏向鎖
當執行緒執行到臨界區(critical section)時,此時會利用CAS(Compare and Swap)操作,將執行緒ID插入到Markword中,同時修改偏向鎖的標志位,
臨界區:就是只允許一個執行緒進去執行操作的區域,即同步代碼塊,只要對多執行緒并發有影響的都叫臨界區,CAS是一個原子性操作
此時Mark word的結構資訊如下:
| bit fields | 是否偏向鎖 | 鎖標志位 | |
|---|---|---|---|
| threadId | epoch | 1 | 01 |
偏向鎖的狀態為“1”,說明物件的偏向鎖生效了,同時也可以看到那個執行緒獲取了該物件的鎖
偏向鎖:jdk1.6引入的一項鎖優化,其中的“偏”是偏心的偏,它的意思就是說,這個鎖會偏向于第一個獲得它的執行緒,在接下來的執行程序中,假如該鎖沒有被其他執行緒所獲取,沒有其他執行緒來競爭該鎖,那么持有偏向鎖的執行緒將永遠不需要進行同步操作,
也就是說:
在此執行緒之后的執行程序中,如果再次進入或者退出同一段同步塊代碼,并不再需要去進行加鎖或者解鎖操作,而是會做以下的步驟:
- Load-and-test,也就是簡單判斷一下當前執行緒id是否與Markword當中的執行緒id是否一致,
- 如果一致,則說明此執行緒已經成功獲得了鎖,繼續執行下面的代碼,
- 如果不一致,則要檢查一下物件是否還是可偏向,即“是否偏向鎖”標志位的值,
- 如果還未偏向,則利用CAS操作來競爭鎖,也即是第一次獲取鎖時的操作,
如果此物件已經偏向了,并且不是偏向自己,則說明存在了競爭,此時可能就要根據另外執行緒的情況,可能是重新偏向,也有可能是做偏向撤銷,但大部分情況下就是升級成輕量級鎖了,
偏向鎖是針對于一個執行緒而言的,執行緒獲得鎖之后就不會再有解鎖等操作了,這樣可以省略很多開銷,假如有兩個執行緒來競爭該鎖話,那么偏向鎖就失效了,進而升級成輕量級鎖了,
為什么要這樣做呢?因為經驗表明,其實大部分情況下,都會是同一個執行緒進入同一塊同步代碼塊的,這也是為什么會有偏向鎖出現的原因,
在Jdk1.6中,偏向鎖的開關是默認開啟的,適用于只有一個執行緒訪問同步塊的場景,
鎖膨脹
當出現有兩個執行緒來競爭鎖的話,那么偏向鎖就失效了,此時鎖就會膨脹,升級為輕量級鎖,這也是我們經常所說的鎖膨脹
鎖撤銷
由于偏向鎖失效了,那么接下來就得把該鎖撤銷,鎖撤銷的開銷花費還是挺大的,其大概的程序如下:
- 在一個安全點停止擁有鎖的執行緒,
- 遍歷執行緒堆疊,如果存在鎖記錄的話,需要修復記錄和 Markword,使其變成無鎖狀態,
- 喚醒當前執行緒,將當前鎖升級成輕量級鎖,
所以如果某些同步代碼塊大多數情況下都是有兩個及以上的執行緒競爭的話,那么偏向鎖就會是一種累贅,對于這種情況,我們一開始就把偏向鎖默認關閉,
輕量級鎖
鎖撤銷升級為輕量級鎖之后,那么物件的Markword也會進行相應的變化,下面先簡單描述下鎖撤銷之后,升級為輕量級鎖的程序:
- 執行緒在自己的堆疊楨中創建鎖記錄LockRecord,
- 將鎖物件的物件頭中的MarkWord復制到執行緒的剛剛創建的鎖記錄中‘
- 將鎖記錄中的Owner指標指向鎖物件,
- 將鎖物件的物件頭的Markword替換為指向偏向鎖記錄的指標,
對應圖描述如下:


此刻Markword:
| bit ields | 鎖標志位 |
|---|---|
| 指向LockRecord的指標 | 00 |
鎖標志位“00”標示輕量級鎖
輕量級鎖主要有兩種
- 自旋鎖
- 自適應
自旋鎖
所謂自旋,就是指當有另外一個執行緒來競爭鎖時,這個執行緒會在原地回圈等待,而不是把該執行緒給阻塞,直到那個獲得鎖的執行緒釋放鎖之后,這個執行緒就可以馬上獲得鎖的,
注意,鎖在原地回圈的時候,是會消耗cpu的,就相當于在執行一個啥也沒有的for回圈,
所以,輕量級鎖適用于那些同步代碼塊執行的很快的場景,這樣,執行緒原地等待很短很短的時間就能夠獲得鎖了,
經驗表明,大部分同步代碼塊執行的時間都是很短很短的,也正是基于這個原因,才有了輕量級鎖這么個東西,
自旋鎖的一些問題
- 如果同步代碼塊執行的很慢,需要消耗大量的時間,那么這個時候其他執行緒在原地等待空消耗cpu,
- 本來一個執行緒把鎖釋放后,當前執行緒是能夠獲得鎖的;但是假如這個時候有好幾個執行緒來競爭這個鎖的話,那么有可能當前執行緒會獲取不到這把鎖,繼續等待消耗cpu,甚至有可能一直獲取不到鎖,
基于這個問題,我們必須給執行緒慷訓圈設定一個次數,當執行緒超過了這個次數,我們就認為,繼續使用自旋鎖就不適合了,此時鎖會再次膨脹,升級為重量級鎖,
默認情況下,自旋的次數為10次,用戶可以通過-XX:PreBlockSpin來進行更改,
自適應自旋鎖
所謂自適應自旋鎖就是執行緒慷訓圈等待的自旋次數并非是固定的,而是會動態著根據實際情況來改變自旋等待的次數,
假如一個執行緒1剛剛成功獲得一個鎖,當它把鎖釋放了之后,執行緒2獲得該鎖,并且執行緒2在運行的程序中,此時執行緒1又想來獲得該鎖了,但執行緒2還沒有釋放該鎖,所以執行緒1只能自旋等待,但是虛擬機認為,由于執行緒1剛付訓得過該鎖,那么虛擬機覺得執行緒1這次自旋也是很有可能能夠再次成功獲得該鎖的,所以會延長執行緒1自旋的次數,
另外,如果對于某一個鎖,一個執行緒自旋之后,很少成功獲得該鎖,那么以后這個執行緒要獲取該鎖時,是有可能直接忽略掉自旋程序,直接升級為重量級鎖的,以免慷訓圈等待浪費資源,
輕量級鎖也被稱為非阻塞同步、樂觀鎖,因為這個程序并沒有把執行緒阻塞掛起,而是讓執行緒慷訓圈等待,串行執行,
重量級鎖
輕量級鎖膨脹之后,就升級為重量級鎖了,重量級鎖是依賴物件內部的monitor鎖來實作的,而monitor又依賴作業系統的MutexLock(互斥鎖)來實作的,所以重量級鎖也被成為互斥鎖,
當輕量級所經過鎖撤銷等步驟升級為重量級鎖之后,它的Markword部分資料大體如下:
| bit fields | 鎖標志位 |
|---|---|
| 指向Mutex的指標 | 10 |
為什么重量級鎖開銷大呢
主要是,當系統檢查到鎖是重量級鎖之后,會把等待想要獲得鎖的執行緒進行阻塞,被阻塞的執行緒不會消耗cup,但是阻塞或者喚醒一個執行緒時,都需要作業系統來幫忙,這就需要從用戶態轉換到內核態,而轉換狀態是需要消耗很多時間的,有可能比用戶執行代碼的時間還要長,
這就是說為什么重量級執行緒開銷很大的
互斥鎖(重量級鎖)也稱為阻塞同步、悲觀鎖
總結
通過上面的分析,我們知道了為什么synchronized關鍵字為何又深得人心,也知道了鎖的演變程序,
也就是說,synchronized關鍵字并非一開始就該物件加上重量級鎖,也是從偏向鎖,輕量級鎖,再到重量級鎖的程序,
這個程序也告訴我們,假如我們一開始就知道某個同步代碼塊的競爭很激烈、很慢的話,那么我們一開始就應該使用重量級鎖了,從而省掉一些鎖轉換的開銷,
下一篇:volatile關鍵字(持續學習更新中)
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/265642.html
標籤:其他
上一篇:小結
