注:無論是對一個對象進(jìn)行加鎖還是對一個方法進(jìn)行加鎖油挥,實際上都是對對象進(jìn)行加鎖腺毫。
java對象在內(nèi)存中的存儲結(jié)構(gòu)主要有一下三個部分:
- 對象頭
- 實例數(shù)據(jù)
- 填充數(shù)據(jù)
這里強(qiáng)調(diào)一下库快,對象頭里的數(shù)據(jù)主要是一些運(yùn)行時的數(shù)據(jù)晤硕。
其簡單的結(jié)構(gòu)如下
從該表格中我們可以看到悼潭,對象中關(guān)于鎖的信息是存在Markword里的
當(dāng)我們創(chuàng)建一個對象LockObject時,該對象的部分Markword關(guān)鍵數(shù)據(jù)如下:
從圖中可以看出舞箍,偏向鎖的標(biāo)志位是“01”舰褪,狀態(tài)是“0”,表示該對象還沒有被加上偏向鎖疏橄。(“1”是表示被加上偏向鎖)占拍。該對象被創(chuàng)建出來的那一刻,就有了偏向鎖的標(biāo)志位,這也說明了所有對象都是可偏向的刷喜,但所有對象的狀態(tài)都為“0”,也同時說明所有被創(chuàng)建的對象的偏向鎖并沒有生效立砸。
偏向鎖:
- 偏向鎖是jdk1.6以后引入的一項鎖優(yōu)化掖疮,其中的“偏”其實是偏心的偏。它的意思是說颗祝,這個鎖會偏向于第一個獲取它的線程浊闪,在接下來的執(zhí)行過程中,假如該鎖沒有其他線程獲取螺戳,也沒有其他線程競爭搁宾,那么持有偏向鎖的線程永遠(yuǎn)不需要進(jìn)行同步操作。
- 偏向鎖是針對一個線程而言的倔幼,線程獲取鎖以后就不需要再有解鎖等操作了盖腿,這樣可以省去很多開銷。假如有兩個以上的線程競爭該鎖损同,那么偏向鎖就失效了翩腐,進(jìn)而升級為輕量級鎖(自旋鎖)
為什么會這樣做呢?因為經(jīng)驗表明膏燃,其實大部分情況下茂卦,都是同一個線程進(jìn)入同一塊同步代碼塊的,這也是設(shè)計偏向鎖的原因
在Jdk1.6中组哩,偏向鎖的開關(guān)是默認(rèn)開啟的等龙,適用于只有一個線程訪問同步塊的場景。
鎖膨脹
假如有兩個以上的線程競爭該鎖伶贰,那么偏向鎖就失效了蛛砰,進(jìn)而升級為輕量級鎖(自旋鎖)。這就是所謂的鎖膨脹
鎖撤銷
由于偏向鎖失效了幕袱,接下來就需要把該鎖撤銷暴备。撤銷鎖的代價還是挺大的,其大概的過程如下:
- 在一個安全點停止擁有該鎖的線程
- 遍歷線程棧们豌,如果存在鎖記錄的話涯捻,需要修復(fù)鎖記錄和Markword,使其變成無鎖狀態(tài)
- 喚醒當(dāng)前線程望迎,將當(dāng)前鎖升級為輕量級鎖(自旋鎖)
所以障癌,如果某些同步代碼塊大多數(shù)情況下都是兩個及以上線程競爭的話,那么偏向鎖就是一種累贅辩尊。對于這種情況涛浙,應(yīng)該一開始就把偏向鎖這個功能關(guān)閉
輕量級鎖(樂觀鎖、非阻塞同步)
鎖撤銷升級為輕量級鎖之后,相應(yīng)的Markword也會進(jìn)行相應(yīng)的變化轿亮。鎖撤銷后升級為輕量級鎖的過程如下:
- 線程在自己的棧幀中創(chuàng)建鎖記錄LockRecord疮薇。
- 將鎖對象的對象頭中的MarkWord復(fù)制到線程的剛剛創(chuàng)建的鎖記錄中。
- 將鎖記錄中的Owner指針指向鎖對象我注。
- 將鎖對象的對象頭的MarkWord替換為指向鎖記錄的指針按咒。
注:鎖標(biāo)志位”00”表示輕量級鎖
輕量級鎖主要有兩種 - 自旋鎖
- 自適應(yīng)自旋鎖
自旋鎖
所謂自旋,就是當(dāng)有另外一個線程來競爭鎖時但骨,這個線程會在原地循環(huán)等待励七,而不是把線程阻塞,直到那個線程獲得鎖的線程釋放鎖之后奔缠,這個線程馬上就可以獲得該鎖掠抬。
注意:鎖在原地循環(huán)的時候,是會消耗CPU的校哎,就相當(dāng)于在執(zhí)行一個什么都沒有的for循環(huán)两波。
所以,輕量級鎖適用于那些同步代碼塊執(zhí)行很快的場景贬蛙,這樣雨女,原地自旋的線程只需要等待很短的時間就可以獲取鎖了。
經(jīng)驗表明阳准,大部分同步代碼塊執(zhí)行的時間都是很短的氛堕,也正是由于這個原因,才有了輕量級鎖
自旋鎖會帶來的一些問題
- 如果同步代碼塊執(zhí)行很慢野蝇,需要消耗大量的時間讼稚,那么這時其他線程會在原地等待很久,消耗CPU
- 本來一個線程把鎖釋放以后绕沈,當(dāng)前線程是能獲取到鎖的锐想。但是假如這個時候有好幾個線程都在競爭這個鎖的話,那么有可能當(dāng)前的線程就獲取不到鎖了乍狐,還需要繼續(xù)原地等待消耗CPU赠摇,甚至有可能一直獲取不到鎖
基于這個問題,我們必須給線程的循環(huán)次數(shù)設(shè)置一個次數(shù)浅蚪,當(dāng)線程自旋超過這個次數(shù)藕帜,我們就認(rèn)為繼續(xù)自旋就不合適了,此時鎖會再次膨脹惜傲,升級為重量級鎖(互斥鎖) - 默認(rèn)情況下洽故,自旋的次數(shù)為10次,用戶可以通過-XX:PreBlockSpin來進(jìn)行更改盗誊。
- 自旋鎖是在jdk1.4.2引入的
自適應(yīng)自旋鎖
所謂自適應(yīng)自旋鎖就是線程空循環(huán)等待的自旋次數(shù)并非是固定的时甚,而是會動態(tài)著根據(jù)實際情況來改變自旋等待的次數(shù)
其大概原理是這樣的:
假如一個線程1剛剛成功獲得一個鎖隘弊,當(dāng)它把鎖釋放了之后,線程2獲得該鎖荒适,并且線程2在運(yùn)行的過程中梨熙,此時線程1又想來獲得該鎖了,但線程2還沒有釋放該鎖刀诬,所以線程1只能自旋等待串结,但是虛擬機(jī)認(rèn)為,由于線程1剛剛獲得過該鎖舅列,那么虛擬機(jī)覺得線程1這次自旋也是很有可能能夠再次成功獲得該鎖的,所以會延長線程1自旋的次數(shù)卧蜓。
另外帐要,如果對于某一個鎖,一個線程自旋之后弥奸,很少成功獲得該鎖榨惠,那么以后這個線程要獲取該鎖時,是有可能直接忽略掉自旋過程盛霎,直接升級為重量級鎖的赠橙,以免空循環(huán)等待浪費資源。
輕量級鎖也被稱為非阻塞同步愤炸、樂觀鎖期揪,因為這個過程并沒有把線程阻塞掛起,而是讓線程空循環(huán)等待规个,串行執(zhí)行凤薛。
重量級鎖(互斥鎖、阻塞同步诞仓、悲觀鎖)
輕量級鎖膨脹之后缤苫,就升級為重量級鎖了。重量級鎖是依賴對象內(nèi)部的monitor鎖來實現(xiàn)的墅拭,而monitor又依賴操作系統(tǒng)的MutexLock(互斥鎖)來實現(xiàn)的活玲,所以重量級鎖也叫互斥鎖。
當(dāng)輕量級鎖經(jīng)過鎖撤銷等步驟升級為重量級鎖之后谍婉,他的Markword部署數(shù)據(jù)體如下:
為什么說重量級鎖開銷大
主要是舒憾,當(dāng)線程檢測到鎖是重量級鎖之后,會把等待獲取鎖的線程阻塞屡萤,被阻塞的線程不會消耗CPU珍剑。但是阻塞或者喚醒一個線程時都要操作系統(tǒng)來幫忙,這就需要從用戶態(tài)切換到內(nèi)核態(tài)死陆,而切換狀態(tài)是需要消耗很多時間的招拙,有時可能比用戶執(zhí)行代碼的時間還要長唧瘾,這就是為什么說重量級鎖開銷很大。
總結(jié)
通過上面的分析别凤,我們知道了為什么synchronized關(guān)鍵字為何又深得人心饰序,也知道了鎖的演變過程。
也就是說规哪,synchronized關(guān)鍵字并非一開始就該對象加上重量級鎖求豫,也是從偏向鎖,輕量級鎖诉稍,再到重量級鎖的過程蝠嘉。