偏向鎖的獲取
當(dāng)一個(gè)線程訪問同步塊并獲取鎖時(shí),會(huì)在對(duì)象頭和棧幀中的鎖記錄里存儲(chǔ)鎖偏向的線程ID,以后該線程在進(jìn)入和退出同步塊時(shí)不需要花費(fèi)CAS操作來加鎖和解鎖,而只需簡(jiǎn)單的測(cè)試一下對(duì)象頭的Mark Word里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖,如果測(cè)試成功,表示線程已經(jīng)獲得了鎖,如果測(cè)試失敗,則需要再測(cè)試下Mark Word中偏向鎖的標(biāo)識(shí)是否設(shè)置成1(表示當(dāng)前是偏向鎖),如果沒有設(shè)置,則使用CAS競(jìng)爭(zhēng)鎖,如果設(shè)置了,則嘗試使用CAS將對(duì)象頭的偏向鎖指向當(dāng)前線程。
偏向鎖的撤銷
偏向鎖使用了一種等到競(jìng)爭(zhēng)出現(xiàn)才釋放鎖的機(jī)制,所以當(dāng)其他線程嘗試競(jìng)爭(zhēng)偏向鎖時(shí),持有偏向鎖的線程才會(huì)釋放鎖。偏向鎖的撤銷,需要等待全局安全點(diǎn)(在這個(gè)時(shí)間點(diǎn)上沒有字節(jié)碼正在執(zhí)行),它會(huì)首先暫停擁有偏向鎖的線程,然后檢查持有偏向鎖的線程是否活著,如果線程不處于活動(dòng)狀態(tài),則將對(duì)象頭設(shè)置成無鎖狀態(tài),如果線程仍然活著,擁有偏向鎖的棧會(huì)被執(zhí)行,遍歷偏向?qū)ο蟮逆i記錄,棧中的鎖記錄和對(duì)象頭的Mark Word要么重新偏向于其他線程,要么恢復(fù)到無鎖或者標(biāo)記對(duì)象不適合作為偏向鎖,最后喚醒暫停的線程。下圖中的線程1演示了偏向鎖初始化的流程,線程2演示了偏向鎖撤銷的流程。
偏向鎖的設(shè)置
關(guān)閉偏向鎖:偏向鎖在Java 6和Java 7里是默認(rèn)啟用的,但是它在應(yīng)用程序啟動(dòng)幾秒鐘之后才激活,如有必要可以使用JVM參數(shù)來關(guān)閉延遲-XX:BiasedLockingStartupDelay = 0。如果你確定自己應(yīng)用程序里所有的鎖通常情況下處于競(jìng)爭(zhēng)狀態(tài),可以通過JVM參數(shù)關(guān)閉偏向鎖-XX:-UseBiasedLocking=false,那么默認(rèn)會(huì)進(jìn)入輕量級(jí)鎖狀態(tài)。
3.2 自旋鎖
線程的阻塞和喚醒需要CPU從用戶態(tài)轉(zhuǎn)為核心態(tài),頻繁的阻塞和喚醒對(duì)CPU來說是一件負(fù)擔(dān)很重的工作。同時(shí)我們可以發(fā)現(xiàn),很多對(duì)象鎖的鎖定狀態(tài)只會(huì)持續(xù)很短的一段時(shí)間,例如整數(shù)的自加操作,在很短的時(shí)間內(nèi)阻塞并喚醒線程顯然不值得,為此引入了自旋鎖。
所謂“自旋”,就是讓線程去執(zhí)行一個(gè)無意義的循環(huán),循環(huán)結(jié)束后再去重新競(jìng)爭(zhēng)鎖,如果競(jìng)爭(zhēng)不到繼續(xù)循環(huán),循環(huán)過程中線程會(huì)一直處于running狀態(tài),但是基于JVM的線程調(diào)度,會(huì)出讓時(shí)間片,所以其他線程依舊有申請(qǐng)鎖和釋放鎖的機(jī)會(huì)。
自旋鎖省去了阻塞鎖的時(shí)間空間(隊(duì)列的維護(hù)等)開銷,但是長(zhǎng)時(shí)間自旋就變成了“忙式等待”,忙式等待顯然還不如阻塞鎖。所以自旋的次數(shù)一般控制在一個(gè)范圍內(nèi),例如10,100等,在超出這個(gè)范圍后,自旋鎖會(huì)升級(jí)為阻塞鎖。
對(duì)自旋鎖周期的選擇上,HotSpot認(rèn)為最佳時(shí)間應(yīng)是一個(gè)線程上下文切換的時(shí)間,但目前并沒有做到。經(jīng)過調(diào)查,目前只是通過匯編暫停了幾個(gè)CPU周期,除了自旋周期選擇,HotSpot還進(jìn)行許多其他的自旋優(yōu)化策略,具體如下:
如果平均負(fù)載小于CPUs則一直自旋
如果有超過(CPUs/2)個(gè)線程正在自旋,則后來線程直接阻塞
如果正在自旋的線程發(fā)現(xiàn)Owner發(fā)生了變化則延遲自旋時(shí)間(自旋計(jì)數(shù))或進(jìn)入阻塞 如果CPU處于節(jié)電模式則停止自旋
自旋時(shí)間的最壞情況是CPU的存儲(chǔ)延遲(CPU A存儲(chǔ)了一個(gè)數(shù)據(jù),到CPU B得知這個(gè)數(shù)據(jù)直接的時(shí)間差)
3.3 輕量級(jí)鎖
輕量級(jí)鎖加鎖
線程在執(zhí)行同步塊之前,JVM會(huì)先在當(dāng)前線程的棧楨中創(chuàng)建用于存儲(chǔ)鎖記錄的空間,并將對(duì)象頭中的Mark Word復(fù)制到鎖記錄中,官方稱為Displaced Mark Word。 然后線程嘗試使用CAS將對(duì)象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當(dāng)前線程獲得鎖,如果失敗,則自旋獲取鎖,當(dāng)自旋獲取鎖仍然失敗時(shí),表示存在其他線程競(jìng)爭(zhēng)鎖(兩條或兩條以上的線程競(jìng)爭(zhēng)同一個(gè)鎖),則輕量級(jí)鎖會(huì)膨脹成重量級(jí)鎖。
輕量級(jí)鎖解鎖
輕量級(jí)解鎖時(shí),會(huì)使用原子的CAS操作來將Displaced Mark Word替換回到對(duì)象頭,如果成功,則表示同步過程已完成。如果失敗,表示有其他線程嘗試過獲取該鎖,則要在釋放鎖的同時(shí)喚醒被掛起的線程。下圖是兩個(gè)線程同時(shí)爭(zhēng)奪鎖,導(dǎo)致鎖膨脹的流程圖。

