問:為什么會(huì)有鎖升級(jí)的過程呢聪蘸?
答:在java6以前synchronized鎖實(shí)現(xiàn)都是重量級(jí)鎖的形式扎附,效率低下第煮,為了提升效率進(jìn)行了優(yōu)化,所以出現(xiàn)了鎖升級(jí)的過程放仗。
問:我們通常說synchronized鎖是重量級(jí)鎖润绎,那么為什么叫他重量級(jí)鎖?
答:因?yàn)閟ynchronized執(zhí)行效率太低匙监。在java1.6以前每次調(diào)用synchronized加鎖時(shí)都需要進(jìn)行系統(tǒng)調(diào)用凡橱,系統(tǒng)調(diào)用會(huì)涉及到用戶態(tài)和內(nèi)核態(tài)的切換,系統(tǒng)調(diào)用會(huì)經(jīng)過0x80中斷亭姥,經(jīng)過內(nèi)核調(diào)用后再返回用戶態(tài)。此過程比較復(fù)雜時(shí)間比較長所以通常叫synchronized為重量級(jí)鎖顾稀。
誤區(qū):其實(shí)鎖升級(jí)過程中涉及到的鎖偏向鎖达罗,輕量級(jí)鎖都是synchronized鎖的具體實(shí)現(xiàn)所要經(jīng)歷的過程,他們并不是單獨(dú)的鎖静秆。只是給他們這幾種鎖的狀態(tài)起了一個(gè)名字而已粮揉。
CAS
在介紹synchronized鎖升級(jí)過程之前,我們需要先了解cas的原理抚笔,為什么呢扶认?因?yàn)閏as貫穿了整個(gè)synchronized鎖升級(jí)的過程。
CAS : compare and swap 或者 compare and exchange 比較交換殊橙。
當(dāng)我們需要對(duì)內(nèi)存中的數(shù)據(jù)進(jìn)行修改操作時(shí)辐宾,為了避免多線程并發(fā)修改的情況,我們在對(duì)它進(jìn)行修改操作前膨蛮,先讀取它原來的值E叠纹,然后進(jìn)行計(jì)算得出新的的值V,在修改前去比較當(dāng)前內(nèi)存中的值N是否和我之前讀到的E相同敞葛,如果相同誉察,認(rèn)為其他線程沒有修改過內(nèi)存中的值,如果不同惹谐,說明被其他線程修改了持偏,這時(shí)驼卖,要繼續(xù)循環(huán)去獲取最新的值E,再進(jìn)行計(jì)算和比較鸿秆,直到我們預(yù)期的值和當(dāng)前內(nèi)存中的值相等時(shí)款慨,再對(duì)數(shù)據(jù)執(zhí)行修改操作。
CAS具體流程如下下圖:
它是為了實(shí)現(xiàn)java中的原子操作而出現(xiàn)的谬莹。為了保證在比較完成后賦值這兩個(gè)操作的原子性檩奠,jvm內(nèi)部實(shí)現(xiàn)cas操作時(shí)通過LOCK CMPXCHG指令鎖cpu總線方式實(shí)現(xiàn)原子操作的。
對(duì)象頭
synchronized用的鎖是存在java對(duì)象頭里的附帽。
32位java對(duì)象頭結(jié)構(gòu)如下表所示:
對(duì)于64位的java對(duì)象頭其余信息基本不變埠戳,只是中間有關(guān)于對(duì)象hashcode值和之后加鎖信息的位數(shù)加大以外,其他基本不變蕉扮。
64位虛擬機(jī)系統(tǒng)下java對(duì)象頭在不同鎖狀態(tài)下的狀態(tài)變化如下表所示:
如上圖所示:其中最后兩位代表是否加鎖的標(biāo)志位整胃。鎖標(biāo)志位如果是01的話需要根據(jù)前一位的是否為偏向鎖來判斷當(dāng)前的鎖狀態(tài),如果前一位為0則代表無鎖狀態(tài)喳钟,如果為1則代表有偏向鎖屁使。
后兩位:00代表輕量級(jí)鎖,10代表重量級(jí)鎖奔则,11代表GC垃圾回收的標(biāo)記信息蛮寂。
偏向鎖
偏向鎖產(chǎn)生的原因?
大多數(shù)情況下易茬,鎖不僅不存在多線程競爭酬蹋,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價(jià)更低而引入了偏向鎖抽莱。
獲取偏向鎖流程:
當(dāng)一個(gè)線程訪問同步塊時(shí)范抓,會(huì)先判斷鎖標(biāo)志位是否為01,如果是01食铐,則判斷是否為偏向鎖匕垫,如果是,會(huì)先判斷當(dāng)前鎖對(duì)象頭中是否存儲(chǔ)了當(dāng)前的線程id虐呻,如果存儲(chǔ)了象泵,則直接獲得鎖。如果對(duì)象頭中指向不是當(dāng)前線程id铃慷,則通過CAS嘗試將自己的線程id存儲(chǔ)進(jìn)當(dāng)前鎖對(duì)象的對(duì)象頭中來獲取偏向鎖单芜。當(dāng)cas嘗試獲取偏向鎖成功后則繼續(xù)執(zhí)行同步代碼塊,否則等待安全點(diǎn)的到來撤銷原來線程的偏向鎖犁柜,撤銷時(shí)需要暫停原持有偏向鎖的線程洲鸠,判斷線程是否活動(dòng)狀態(tài),如果已經(jīng)退出同步代碼塊則喚醒新的線程開始獲取偏向鎖,否則開始鎖競爭進(jìn)行鎖升級(jí)過程扒腕,升級(jí)為輕量級(jí)鎖绢淀。
偏向鎖獲取流程如下圖:
在高并發(fā)下可以關(guān)閉偏向鎖來提升性能,通過設(shè)置JVM參數(shù) -XX:-UseBiasedLocking=false瘾腰。
輕量級(jí)鎖
當(dāng)出現(xiàn)鎖競爭時(shí)皆的,會(huì)升級(jí)為輕量級(jí)鎖。
在升級(jí)輕量級(jí)鎖之前蹋盆,JVM會(huì)先在當(dāng)前線程的棧幀中創(chuàng)建用于存儲(chǔ)鎖記錄的空間即將對(duì)象頭中用來標(biāo)記鎖信息相關(guān)的內(nèi)容封裝成一個(gè)java對(duì)象放入當(dāng)前線程的棧幀中费薄,這個(gè)對(duì)象稱為LockRcord,然后線程嘗試通過CAS將對(duì)象頭中mark word替換為指向鎖記錄(lockrecord)的指針栖雾。如果成功則當(dāng)前線程獲取鎖楞抡,如果失敗則使用自旋來獲取鎖。自旋其實(shí)就是不斷的循環(huán)進(jìn)行CAS操作直到能成功替換析藕。所以輕量級(jí)鎖又叫自旋鎖召廷。
下圖來源于網(wǎng)絡(luò)
棧上分配LockRecord: lockrecord中包含了對(duì)象的引用地址。
對(duì)象頭中markword替換鎖記錄指針成功之后如下圖:
替換成功之后將鎖標(biāo)志位改為00 表示獲取輕量級(jí)鎖成功账胧。
lockrecord的作用:在這里實(shí)現(xiàn)了鎖重入竞慢,每當(dāng)同一個(gè)線程多次獲取同一個(gè)鎖時(shí),會(huì)在當(dāng)前棧幀中放入一個(gè)lockrecord治泥,但是重入是放入的lockrecord關(guān)于鎖信息的內(nèi)容為null筹煮,代表鎖重入。當(dāng)輕量級(jí)解鎖時(shí)车摄,每解鎖一次則從棧幀中彈出一個(gè)lockrecord寺谤,直到為0.
輕量級(jí)鎖重入之后如下圖:
當(dāng)通過CAS自旋獲取輕量級(jí)鎖達(dá)到一定次數(shù)時(shí),JVM會(huì)發(fā)生鎖膨脹升級(jí)為重量級(jí)鎖吮播。
原因:不斷的自旋在高并發(fā)的下會(huì)消耗大量的cpu資源,所以jvm為了節(jié)省cpu資源眼俊,進(jìn)行了鎖升級(jí)意狠。將等待獲取鎖的線程都放入一個(gè)等待隊(duì)列中來節(jié)省cpu資源。
重量級(jí)鎖
在重量級(jí)鎖中將LockRecord對(duì)象替換為了monitor對(duì)象的實(shí)現(xiàn)疮胖。主要通過monitorenter和monitorexit兩個(gè)指令來實(shí)現(xiàn)环戈。需要經(jīng)過系統(tǒng)調(diào)用,在并發(fā)低的情況下效率會(huì)低澎灸。
通過openJDK可以查看ObjectMonitor對(duì)象的結(jié)構(gòu):
http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/9758d9f36299/src/share/vm/runtime/objectMonitor.hpp
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL; //擁有當(dāng)前對(duì)象的線程
_WaitSet = NULL; //阻塞隊(duì)列
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; //有資格成為候選資源的線程隊(duì)列
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
使用monitor加鎖如下圖:
重量級(jí)鎖在進(jìn)行鎖重入的時(shí)候每獲取到鎖一次會(huì)對(duì)monitor對(duì)象中的計(jì)數(shù)器+1院塞,等鎖退出時(shí)則會(huì)相應(yīng)的-1,直到減到0為止性昭,鎖完全退出拦止。
幾種鎖狀態(tài)優(yōu)缺點(diǎn)對(duì)比
https://p26.toutiaoimg.com/origin/pgc-image/a983c3bbdf814797add6d3a7515dfe10?from=pc
總結(jié)
綜上,我們發(fā)現(xiàn)偏向鎖,輕量級(jí)鎖(又稱自旋鎖或無鎖)汹族,重量級(jí)鎖都是synchronized鎖鎖實(shí)現(xiàn)中鎖經(jīng)歷的幾種不同的狀態(tài)萧求。
三種鎖狀態(tài)的場景總結(jié):
- 只有一個(gè)線程進(jìn)入臨界區(qū) -------偏向鎖
- 多個(gè)線程交替進(jìn)入臨界區(qū)--------輕量級(jí)鎖
- 多個(gè)線程同時(shí)進(jìn)入臨界區(qū)-------重量級(jí)鎖
作者:little_color
原文鏈接:
https://blog.csdn.net/wangyy130/article/details/106495180