最近正在看《并發(fā)編程的藝術(shù)》這本書,因?yàn)橹耙查喿x了大量關(guān)于多線程的博客控轿,所以讀起來(lái)還是很流暢的冤竹,基本沒有遇到什么問(wèn)題。但是就書中Synchronized優(yōu)化這一部分產(chǎn)生了小小的疑慮茬射。偏向鎖何時(shí)膨脹為輕量級(jí)鎖鹦蠕,輕量級(jí)鎖何時(shí)又膨脹為重量級(jí)鎖,因此就產(chǎn)生了這篇文章在抛。
Synchronized是這篇文章的主角钟病。相對(duì)于保證可見性的關(guān)鍵字volatiley以及各種并發(fā)安全的concurrent容器,隱式鎖Synchronized確實(shí)顯得有些重了霜定。但是有很多復(fù)雜的多線程并發(fā)情況確實(shí)需要鎖來(lái)保證并發(fā)安全档悠,因此Java1.6里對(duì)Synchronized進(jìn)行了優(yōu)化。在只有單線程重復(fù)執(zhí)行同步代碼(使用偏向鎖)望浩,以及多線程無(wú)爭(zhēng)搶輪流執(zhí)行同步代碼(使用輕量級(jí)鎖)兩種場(chǎng)景下極大地提升Synchronized的性能辖所。對(duì)于多線程爭(zhēng)搶這一場(chǎng)景,Synchronized依舊使用重量級(jí)鎖磨德,沒有變化缘回。
鎖的膨脹
java中鎖一共有4種狀態(tài),級(jí)別從低到高依次是:無(wú)鎖狀態(tài)、偏向鎖狀態(tài)典挑、輕量級(jí)鎖狀態(tài)和重量級(jí)鎖狀態(tài),這幾個(gè)狀態(tài)會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí)酥宴。鎖可以升級(jí)但不能降級(jí),意味著偏向鎖升級(jí)成輕量級(jí)鎖后不能降級(jí)成偏向鎖。這種鎖升級(jí)卻不能降級(jí)的策略,目的是為了提高獲得鎖和釋放鎖的效率您觉。
偏向鎖的膨脹
鎖是存在Java對(duì)象頭里的拙寡。在最開始一個(gè)單線程通過(guò)同步代碼塊的時(shí)候,會(huì)給Synchronized的對(duì)象頭里加上偏向鎖琳水,并將自己的線程ID通過(guò)CAS存儲(chǔ)在對(duì)象頭中肆糕。當(dāng)線程自己再次通過(guò)時(shí),只需要校驗(yàn)一下線程ID就可以重入在孝,效率很高诚啃,這也就是偏向鎖優(yōu)化的單線程場(chǎng)景。這時(shí)假如又來(lái)了一個(gè)需要通過(guò)同步塊的線程私沮,檢查對(duì)象頭中的線程ID不是自己這個(gè)線程始赎。這時(shí)會(huì)去找原來(lái)持有這個(gè)偏向鎖的線程是否還是活動(dòng)線程,如果已經(jīng)不活動(dòng)則將偏向鎖的標(biāo)識(shí)置為0,線程2重做上面的步驟取得偏向鎖造垛。如果原來(lái)持有偏向鎖的線程還是活動(dòng)線程魔招,則暫停原有的持鎖線程,此時(shí)將偏向鎖升級(jí)為輕量級(jí)鎖筋搏。首先將對(duì)象頭的鎖標(biāo)志位改為00仆百,之后在持有偏向鎖的線程的棧楨中創(chuàng)建用于存儲(chǔ)鎖記錄的空間,并將對(duì)象頭中的Mark Word復(fù)制到鎖記錄中,然后線程嘗試使用CAS將對(duì)象頭中的Mark Word替換為指向鎖記錄的指針奔脐。這就完成了鎖的膨脹俄周,之后恢復(fù)持有輕量級(jí)鎖的線程。
輕量級(jí)鎖的膨脹
線程解鎖時(shí)使用CAS將對(duì)象頭中的指針替換回Mark Word髓迎。新來(lái)的線程在持鎖時(shí)使用CAS將對(duì)象頭中的Mark Word替換為指向自己鎖記錄的指針峦朗。這也就是輕量級(jí)鎖優(yōu)化的多線程交替執(zhí)行同步代碼的場(chǎng)景。當(dāng)以上兩種CAS操作失敗時(shí)排龄,輕量級(jí)鎖膨脹為重量級(jí)鎖波势,新入的線程自旋等待鎖,自旋一段時(shí)間還無(wú)法取得鎖時(shí)線程堵塞橄维。這與《并發(fā)編程的藝術(shù)》中描述的可能有些許不同尺铣,書中描述自旋一定時(shí)間還是持鎖失敗后輕量級(jí)鎖才膨脹為重量級(jí)鎖。而自旋這個(gè)動(dòng)作本身就是JVM針對(duì)重量級(jí)鎖的優(yōu)化争舞,所以只要CAS失敗產(chǎn)生競(jìng)爭(zhēng)后凛忿,輕量級(jí)鎖就進(jìn)行了膨脹。