在自己的另一篇文章《JVM學(xué)習(xí)筆記》中提到了在HotSpot虛擬機(jī)中硼控,java對象主要分為對象頭刘陶、實(shí)例信息以及對其填充,而對象頭中又進(jìn)一步包含了Mark Word淀歇、類型指針易核,這里的Mark Word中包含鎖狀態(tài)標(biāo)志等信息匈织,如下圖浪默,本文將進(jìn)一步說明JAVA中的鎖機(jī)制。
一缀匕、自旋鎖和自適應(yīng)自旋
1.1 自旋鎖
一個(gè)線程如果執(zhí)行到synchronized同步方法或者同步代碼塊時(shí)纳决,其他的線程已經(jīng)持有了該對象的鎖,那這個(gè)線程就會被操作系統(tǒng)掛起乡小,等待其他線程執(zhí)行完畢阔加,該線程才能繼續(xù)執(zhí)行。然而满钟,線程的掛起和恢復(fù)都需要從應(yīng)用的用戶態(tài)切換到操作系統(tǒng)的內(nèi)核態(tài)才能完成胜榔,這種操作對性能是有很大影響的。另外湃番,很多時(shí)候共享數(shù)據(jù)上的鎖很快就能被釋放出來夭织,為了這點(diǎn)時(shí)間去掛起和恢復(fù)線程顯得很不值得,這個(gè)時(shí)候就可以讓線程去執(zhí)行一個(gè)忙循環(huán)吠撮,也就是所謂的自旋尊惰,讓請求鎖的線程在JVM層面“稍等一下”,看持有鎖的線程是否能很快將鎖釋放出來,這種技術(shù)弄屡,就是所謂的自旋鎖题禀。
舉一個(gè)不太恰當(dāng)?shù)睦樱热缒阏诤屯潞藢σ粋€(gè)接口的協(xié)議膀捷,此時(shí)產(chǎn)品喊你到會議室評審需求迈嘹,你估摸著馬上和同事就能對完,所以你跟產(chǎn)品說全庸,“等我一下江锨,馬上過來”,而不是將會議掛起糕篇,等待下次再組織啄育。這時(shí)產(chǎn)品也會先到會議室等待人員到齊(忙循環(huán))。
1.2 自適應(yīng)自旋
在上面這個(gè)例子里面拌消,如果產(chǎn)品最后等了很長時(shí)間挑豌,參會的人員一直沒到齊,那其實(shí)對產(chǎn)品自身的時(shí)間也是一種浪費(fèi)墩崩,體現(xiàn)在程序上氓英,就是性能的浪費(fèi),這也是自旋鎖的缺點(diǎn)鹦筹,所以不能沒有限制的自旋铝阐,需要更加“智能”的判斷,也就是所謂的自適應(yīng)自旋的概念铐拐。
自適應(yīng)自旋的時(shí)間不是固定的徘键,是由前一次自旋時(shí)間和持有者的狀態(tài)來決定的,如果前一次自旋成功獲取過鎖遍蟋,則本次自旋等待的時(shí)間可以長一些吹害,否則省略自旋,避免資源的浪費(fèi)虚青。
還是以上面的例子舉例它呀,當(dāng)我跟產(chǎn)品說“等我一下,馬上過來”的時(shí)候棒厘,如果上次會議就因?yàn)榈任业攘撕芫米荽a(chǎn)品可能會說,“那算了奢人,等你有空了我們再評審需求”谓媒。
二、輕量級鎖
為了確保同一時(shí)刻达传,只有一個(gè)線程可以操作一段代碼篙耗,JVM會給每個(gè)對象或者類分配一個(gè)內(nèi)置鎖迫筑,這個(gè)鎖是通過監(jiān)視器(Monitors)來實(shí)現(xiàn)的,然而這種鎖的同步成本非常高宗弯,又被成為“重量級鎖”脯燃。
為了優(yōu)化重量級鎖帶來的性能問題,除了上面提到的自旋鎖外蒙保,如果沒有多線程競爭的情況下辕棚,申請重量級鎖是對性能的浪費(fèi),所以JVM提供了一種通過使用CAS(compare and swap)操作邓厕,嘗試將對象頭中的Mark Word指向棧楨中新建立的鎖記錄(Lock Record)的空間逝嚎,來表示線程持有了該對象的鎖,也就是所謂的輕量級鎖详恼。
輕量級鎖獲取鎖的操作步驟
1 JVM在當(dāng)前線程的棧楨中新建一個(gè)鎖記錄(Lock Record)的空間补君,用于存儲目標(biāo)對象的Mark Word的拷貝
2 JVM嘗試使用CAS操作將該對象的Mark Word指向Lock Record的指針
3 如果第二步的操作成功,則線程持有了該對象的鎖昧互,并且對象的Mark Word狀態(tài)變成(Light-weight locked挽铁,00),表示輕量級鎖定
4 如果第二步操作失敗敞掘,JVM會檢查該對象的Mark Word是否已經(jīng)指向了Lock Record叽掘,如果是則直接進(jìn)入同步代碼塊執(zhí)行,否則說明對象已經(jīng)被其他線程搶占了玖雁。如果有兩個(gè)以上的線程爭同一個(gè)鎖更扁,則該對象的Mark Word的鎖標(biāo)記狀態(tài)更新為“10”(Heavy-weight locked),即膨脹為重量級鎖赫冬,輕量級鎖不再有效浓镜,后續(xù)等待鎖的線程將直接進(jìn)入阻塞狀態(tài)。
三面殖、偏向鎖
如果自始至終都只有一個(gè)線程在使用鎖竖哩,那此時(shí)連輕量級鎖都是對資源的浪費(fèi),偏向鎖的意思就是這個(gè)對象的鎖會偏向于第一個(gè)獲取到它的線程脊僚,如果后面沒有被其他線程獲取,則偏向鎖永遠(yuǎn)也不需要同步遵绰。
偏向鎖的步驟
1.如果開啟了偏向鎖辽幌,那么當(dāng)鎖對象第一次被線程獲取的時(shí)候,JVM會把對象頭的標(biāo)志位置為“01”椿访,Biased/Biasable,同時(shí)CAS操作把獲得鎖的線程ID記錄到Mark Word中乌企,表示該線程持有了該對象的偏向鎖
2.如果CAS操作成功,則持有偏向鎖的線程每次進(jìn)入到相關(guān)的同步代碼塊成玫,JVM均不需要進(jìn)行多余的同步操作
3.當(dāng)有其他的線程嘗試獲取這個(gè)鎖時(shí)加酵,偏向鎖模式結(jié)束拳喻,對象頭恢復(fù)到Unlocked狀態(tài)或者Light-weight locked(輕量級鎖)
四、鎖消除和鎖粗化
除了上述各種鎖外,JVM為了進(jìn)一步優(yōu)化猪腕,還有鎖消除和鎖優(yōu)化兩種機(jī)制冗澈,這里只簡單介紹定義
4.1 鎖消除
JVM在JIT運(yùn)行時(shí),對一些代碼要求同步而實(shí)際該段代碼在數(shù)據(jù)共享上不可能出現(xiàn)競爭的鎖而進(jìn)行消除操作
4.2 鎖粗化
對于相同鎖定對象的相鄰?fù)酱a塊陋葡,JVM為了提高性能亚亲,就會對同步范圍進(jìn)行粗化,把鎖放在第一個(gè)操作加上腐缤,然后在最后一個(gè)操作中釋放捌归,這樣就只加了一次鎖但是達(dá)到了同樣的效果。