Java多線程揭秘之synchronized工作原理
在學(xué)習(xí)本篇文章時(shí),如果有不太懂的地方,大家也可以先看看博主上一篇文章,鎖的這部分內(nèi)容是面試中很常見(jiàn)的問(wèn)題,多學(xué)學(xué)對(duì)自己是非常有幫助的。同時(shí),朋友們?nèi)绻惺裁磫?wèn)題都可以隨時(shí)和我探討,大家一起進(jìn)步!
一. 特性
這部分內(nèi)容在上篇文章中的 synchronized充當(dāng)了哪些鎖部分已經(jīng)介紹過(guò)了哦,沒(méi)有看的小伙伴可以去看看synchronized的特性
二. 加鎖過(guò)程(鎖升級(jí)/鎖膨脹)
在Java中JVM虛擬機(jī)將synchronized鎖分為無(wú)鎖、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖狀態(tài)。會(huì)根據(jù)不同的情況,進(jìn)行不同的升級(jí)操作
1. 無(wú)鎖狀態(tài)
此狀態(tài)理解起來(lái)較為簡(jiǎn)單,沒(méi)有進(jìn)行線程任務(wù)時(shí)最開(kāi)始的狀態(tài)就是無(wú)鎖狀態(tài)。
2. 偏向鎖
- 偏向鎖類似于一種樂(lè)觀鎖,當(dāng)一個(gè)線程在執(zhí)行任務(wù)時(shí),偏向鎖會(huì)給這個(gè)線程設(shè)定一個(gè)標(biāo)記(并不是真正地加鎖),如果后續(xù)沒(méi)有其他線程來(lái)競(jìng)爭(zhēng)這個(gè)鎖,那么這個(gè)偏向鎖就不會(huì)再進(jìn)行其他的任何操作了,有效避免了因?yàn)榧渔i過(guò)程而產(chǎn)生的內(nèi)存開(kāi)銷問(wèn)題
- 若有其他線程也競(jìng)爭(zhēng)這把鎖,那么此時(shí)第一個(gè)線程會(huì)立馬把鎖拿到(因?yàn)橹暗谝粋€(gè)線程已經(jīng)有了偏向鎖標(biāo)記,所以很容易拿到)然后進(jìn)入輕量級(jí)鎖的狀態(tài)
偏向鎖的大體思路是能不加鎖就盡量不加鎖避免內(nèi)存開(kāi)銷,只做上標(biāo)記即可,但如果實(shí)在要加鎖,也會(huì)因?yàn)闃?biāo)記的存在而立馬把鎖拿到(類似于高考填志愿保底心態(tài))
3. 輕量級(jí)鎖
當(dāng)進(jìn)入輕量級(jí)鎖鎖狀態(tài)(自適應(yīng)自旋鎖)后,是完全在用戶態(tài)上實(shí)現(xiàn)的,且是基于CAS來(lái)完成的操作,因?yàn)檫@個(gè)狀態(tài)不涉及到內(nèi)核態(tài)和用戶態(tài)的切換,也不涉及到線程的阻塞和調(diào)度過(guò)程。所以并不會(huì)對(duì)系統(tǒng)的內(nèi)存有著過(guò)于高的開(kāi)銷,因此可以保證更高效地獲取到鎖(一個(gè)線程釋放鎖后,另一個(gè)線程會(huì)馬上獲取到鎖)
具體步驟
- 通過(guò) CAS 檢查并更新一塊內(nèi)存 (比如 null => 該線程引用)
- 如果更新成功, 則認(rèn)為加鎖成功
- 如果更新失敗, 則認(rèn)為鎖被占用, 繼續(xù)自旋式的等待(并不放棄 CPU)由于自旋操作可能會(huì)一直讓CPU 空轉(zhuǎn),比較浪費(fèi) CPU 資源,因此此處的自旋不會(huì)一直持續(xù)進(jìn)行,而是達(dá)到一定的時(shí)間(重試)次數(shù),就不再自旋了,也就是所謂的 “自適應(yīng)”(根據(jù)情況來(lái))
4. 重量級(jí)鎖
當(dāng)鎖的競(jìng)爭(zhēng)變得非常激烈時(shí),如果再按照之前自旋的方式,那么對(duì)于CPU的開(kāi)銷是非常高的,而且此時(shí)自旋還不能快速地獲取到鎖的狀態(tài),那么此時(shí)就會(huì)變成重量級(jí)鎖(掛起等待鎖),對(duì)于掛起等待鎖來(lái)說(shuō),鎖的等待過(guò)程是釋放CPU的過(guò)程,此時(shí)會(huì)節(jié)省CPU的開(kāi)銷,但付出的代價(jià)是引入了線程的阻塞和調(diào)度的開(kāi)銷(以CPU資源換取性能)
具體過(guò)程
此處的重量級(jí)鎖就是用到了內(nèi)核提供的mutex,要執(zhí)行加鎖操作,首先會(huì)進(jìn)入內(nèi)核態(tài),在內(nèi)核態(tài)判定當(dāng)前的鎖是否已經(jīng)被占用,若該鎖沒(méi)有被占用,則加鎖成功,切換回用戶態(tài);若該鎖被占用,則加鎖失敗,此時(shí)線程進(jìn)入鎖的等待隊(duì)列去掛起等待,直到鎖被其他線程釋放后,操作系統(tǒng)才會(huì)喚醒掛起等待鎖的這個(gè)線程,最后這個(gè)線程才會(huì)獲取到鎖
5. 總結(jié)
- 鎖升級(jí)(鎖膨脹)的過(guò)程完全是
synchronized
內(nèi)部自適應(yīng)完成的,即根據(jù)不同的情況(即鎖沖突的高或低狀態(tài))來(lái)升級(jí)或降級(jí)成對(duì)應(yīng)的狀態(tài),不需要用戶或者程序員去干預(yù),因此使用起來(lái)會(huì)比較方便。
- 注意,
synchronized
在有些JVM版本上是可以同時(shí)實(shí)現(xiàn)降級(jí)和升級(jí)的自適應(yīng)的,但在有些JVM上只能實(shí)現(xiàn)升級(jí)的自適應(yīng)。
三. 鎖優(yōu)化
1. 鎖消除
JVM和編譯器提供了一個(gè)很好的功能,能判斷某段代碼是否有加鎖的必要(根據(jù)情況選擇是否需要加鎖),以防開(kāi)發(fā)人員加錯(cuò)鎖而造成無(wú)緣無(wú)故開(kāi)銷很大系統(tǒng)內(nèi)存的情況。
例子
Java提供了兩個(gè)類,StringBuilder
和StringBuffer
,其中,前者不會(huì)考慮線程安全問(wèn)題,而后者中的每個(gè)方法都帶有了synchronized
以確保線程安全。但我們平常在單線程下,這個(gè)加鎖是沒(méi)有必要的,會(huì)白白浪費(fèi)很多內(nèi)存資源,這時(shí)候,如果開(kāi)發(fā)人員不小心在單線程中使用了StringBuffer
,那么編譯器和JVM也會(huì)對(duì)其進(jìn)行一定的優(yōu)化,去把這個(gè)鎖消除。
注意,編譯器的判斷也不是每次完全都是正確的,不會(huì)每次都會(huì)鎖消除,因此,還是要提醒大家在寫(xiě)代碼過(guò)程中自己還是要盡量避免出現(xiàn)此類錯(cuò)誤
2. 鎖粗化
介紹鎖粗化之前,首先大家得知道鎖的粗細(xì)的含義:
- 如果
synchronized
代碼塊中包含的代碼比較多,則認(rèn)為鎖比較粗 - 如果
synchronized
代碼塊中包含的代碼比較少,則認(rèn)為鎖比較細(xì)
那么實(shí)際開(kāi)發(fā)中,到底是鎖粗一點(diǎn)好還是細(xì)一點(diǎn)好呢?這個(gè)還是根據(jù)情況來(lái)決定的,雖然當(dāng)鎖里面的代碼量少時(shí)會(huì)減少線程之間的鎖沖突概率,但是有的情況下,反而當(dāng)鎖里面的代碼量較多時(shí),運(yùn)行效率才會(huì)更高:
void func(){ synchronized (this){ //任務(wù)1 } synchronized (this){ //任務(wù)2 } synchronized (this){ //任務(wù)3 } }
大家先來(lái)看一看這段代碼,這段代碼是細(xì)鎖的情況(一個(gè)鎖中的代碼量較少),那么其執(zhí)行效率怎么樣呢?顯然是不太高的,每次加一次鎖都會(huì)進(jìn)行一定的內(nèi)存開(kāi)銷,因此我們很有必要對(duì)其進(jìn)行一定地改進(jìn),讓鎖粗化:
void func(){ synchronized (this){ //任務(wù)1 //任務(wù)2 //任務(wù)3 } }
可以看出來(lái),當(dāng)鎖粗化的時(shí)候,會(huì)大大提高代碼的執(zhí)行效率
就好比出門(mén)買(mǎi)物品A和物品B,我們通常會(huì)盡可能地出一次門(mén),將二者一塊買(mǎi)好再回家,而不是先把物品A買(mǎi)好放到家里再出門(mén)買(mǎi)物品B,這樣顯然效率是非常低的,而且還很浪費(fèi)體力
到此這篇關(guān)于Java多線程揭秘之synchronized工作原理的文章就介紹到這了,更多相關(guān)Java synchronized內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot開(kāi)發(fā)實(shí)戰(zhàn)之自動(dòng)配置
SpringBoot的核心就是自動(dòng)配置,自動(dòng)配置又是基于條件判斷來(lái)配置Bean,下面這篇文章主要給大家介紹了關(guān)于SpringBoot開(kāi)發(fā)實(shí)戰(zhàn)之自動(dòng)配置的相關(guān)資料,需要的朋友可以參考下2021-08-08解決idea每次新建項(xiàng)目都需要重新指定maven目錄
這篇文章主要介紹了解決idea每次新建項(xiàng)目都需要配置maven,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09IDEA中程序包Org.Springframework.Boot不存在問(wèn)題及解決
這篇文章主要介紹了IDEA中程序包Org.Springframework.Boot不存在問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07Java中while語(yǔ)句的簡(jiǎn)單知識(shí)及應(yīng)用
這篇文章主要給大家介紹了關(guān)于Java中while語(yǔ)句的簡(jiǎn)單知識(shí)及應(yīng)用的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01JSP 開(kāi)發(fā)之hibernate的hql查詢多對(duì)多查詢
這篇文章主要介紹了JSP 開(kāi)發(fā)之hibernate的hql查詢多對(duì)多查詢的相關(guān)資料,希望通過(guò)本文能幫助到大家,需要的朋友可以參考下2017-09-09如何使用會(huì)話Cookie和Java實(shí)現(xiàn)JWT身份驗(yàn)證
這篇文章主要介紹了如何使用會(huì)話Cookie和Java實(shí)現(xiàn)JWT身份驗(yàn)證,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下2021-03-03java簡(jiǎn)單實(shí)現(xiàn)桌球滾動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了java簡(jiǎn)單實(shí)現(xiàn)桌球滾動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-10-10