Java中的鎖與鎖的狀態(tài)升級(jí)詳細(xì)解讀
Java 1.6 的優(yōu)化
Java 1.6以后官方針對(duì)鎖的優(yōu)化,主要是增加了兩種新的鎖:偏向鎖和輕量級(jí)鎖。再加上本身重量級(jí)鎖,那么鎖基本上可以大致分為這三種,它們之間的區(qū)別主要是體現(xiàn)在等待時(shí)間上面。重量級(jí)鎖比較暴力,一旦某個(gè)資源被一個(gè)線(xiàn)程加了鎖,那么其他線(xiàn)程就不要再有任何想法了,必須等待其釋放資源才可以。如果時(shí)間較長(zhǎng),就會(huì)造成程序卡頓,甚至崩潰掉,為了改進(jìn)加了兩個(gè)新鎖。這兩種新鎖的概念是如何優(yōu)化的呢,就需要弄清楚對(duì)象和monitor的關(guān)系。
對(duì)象頭結(jié)構(gòu)
任何一個(gè)實(shí)例對(duì)象都有一些共同的信息即對(duì)象頭、實(shí)例變量、填充數(shù)據(jù)。其中對(duì)象頭就是加鎖的基礎(chǔ),其中存儲(chǔ)的就是加鎖的信息;實(shí)例變量就是存的屬性變量的信息,私有公有之類(lèi)的;填充數(shù)據(jù)則是數(shù)據(jù)的起始地址。我們要說(shuō)的還是對(duì)象頭,下面是對(duì)象頭結(jié)構(gòu):
長(zhǎng)度 | 內(nèi)容 | 描述 |
32/64 bit | Mark Word | 存儲(chǔ)對(duì)象的hashCode或者鎖信息等 |
32/64 bit | Class Metadata Address | 存儲(chǔ)到對(duì)象類(lèi)型數(shù)據(jù)的指針 |
32/64 bit | Array Length | 數(shù)組的長(zhǎng)度,如果當(dāng)前對(duì)象是數(shù)組 |
從對(duì)象頭的信息來(lái)看,Mark Word就是存放鎖信息的地方,一共有32位用來(lái)存放鎖信息,信息格式如下:
鎖的狀態(tài)
首先先解釋下上表中鎖狀態(tài)的含義:
無(wú)鎖狀態(tài):沒(méi)有加鎖,屬于樂(lè)觀鎖,鎖標(biāo)記01。
偏向鎖:在對(duì)象第一次被某一線(xiàn)程占有的時(shí)候,會(huì)檢查偏向鎖標(biāo)記,然后把偏向鎖標(biāo)記置1,表示線(xiàn)程被占用使用的是偏向鎖,鎖標(biāo)記置為01。接著寫(xiě)入線(xiàn)程ID,表示當(dāng)前對(duì)象被哪個(gè)ID的線(xiàn)程占有了。當(dāng)此線(xiàn)程占有期間,其他的線(xiàn)程訪(fǎng)問(wèn)的時(shí)候,就會(huì)有線(xiàn)程競(jìng)爭(zhēng)。如果競(jìng)爭(zhēng)成功,使用資源;如果競(jìng)爭(zhēng)失敗則等待下次競(jìng)爭(zhēng),偏向鎖是輕量級(jí)鎖,屬于樂(lè)觀鎖。所謂的偏向鎖,會(huì)比較傾向于首次占有的線(xiàn)程,也就是說(shuō)兩個(gè)線(xiàn)程競(jìng)爭(zhēng),第一次占有的線(xiàn)程會(huì)有比較大的概率分配到資源,這也就是偏向鎖名稱(chēng)的由來(lái):會(huì)偏向于首先占有該資源的線(xiàn)程。那么當(dāng)前線(xiàn)程一旦具有優(yōu)先權(quán),其他線(xiàn)程很可能長(zhǎng)久無(wú)法獲取資源,為了改進(jìn)這樣一個(gè)狀態(tài),很多時(shí)候會(huì)采用大名鼎鼎非阻塞算法Compare and Swap(CAS)算法。正是因?yàn)檫@種特性,偏向鎖更加適合用于競(jìng)爭(zhēng)不激烈的時(shí)候。由于偏向鎖會(huì)不斷地和無(wú)鎖狀態(tài)進(jìn)行切換,因此兩者使用時(shí)間也是接近的。
輕量級(jí)鎖:一般在線(xiàn)程有交替時(shí)使用。當(dāng)偏向鎖算法CAS競(jìng)爭(zhēng)失敗,就會(huì)修改偏向鎖標(biāo)記為00,升級(jí)為輕量級(jí)鎖,屬于樂(lè)觀鎖。
重量級(jí)鎖:強(qiáng)互斥,鎖住資源后不允許其他線(xiàn)程競(jìng)爭(zhēng),其他線(xiàn)程要想訪(fǎng)問(wèn)必須等待資源釋放,等待時(shí)間長(zhǎng)。當(dāng)某資源已經(jīng)處于輕量級(jí)鎖的時(shí)候,會(huì)有別的線(xiàn)程前來(lái)競(jìng)爭(zhēng),繼續(xù)競(jìng)爭(zhēng)失敗,則會(huì)修改標(biāo)記為10,變?yōu)橹亓考?jí)鎖,屬于悲觀鎖。
自旋鎖與鎖消除
鎖的升級(jí)也是要消耗時(shí)間的,尤其是不同等級(jí)之間的轉(zhuǎn)換消耗的時(shí)間更多。而輕量級(jí)鎖轉(zhuǎn)換成重量級(jí)鎖則是用戶(hù)線(xiàn)程和核心線(xiàn)程(操作系統(tǒng)本身的線(xiàn)程)之間的轉(zhuǎn)換非常耗時(shí),所以我們要盡量減少這種轉(zhuǎn)換,所以有了這樣一種概念:當(dāng)競(jìng)爭(zhēng)失敗的時(shí)候,先不著急進(jìn)行轉(zhuǎn)換,而是等待一會(huì)兒,由于線(xiàn)程執(zhí)行的非常快,可能還沒(méi)有升級(jí)到重量級(jí)鎖的時(shí)候,資源就已經(jīng)被釋放了。正是基于這樣一種邏輯,就有了自旋鎖這一概念。
自旋鎖:當(dāng)競(jìng)爭(zhēng)失敗的時(shí)候,不是馬上轉(zhuǎn)化級(jí)別,而是執(zhí)行幾次空循環(huán),如果執(zhí)行空循環(huán)的時(shí)候,占有線(xiàn)程釋放了資源,那么就不需要進(jìn)行鎖的升級(jí),直接進(jìn)行占用線(xiàn)程執(zhí)行就好了因此自旋鎖只是一種概念,并不是真正的鎖。
鎖消除: Java的即時(shí)編譯編譯器(JIT)在編譯的時(shí)候把不必要的鎖去掉。比如下面代碼,用來(lái)給變量a賦值,就完全沒(méi)有必要加鎖,因此這個(gè)synchronized在編譯的時(shí)候會(huì)被消去。
synchronized(this){ int a=1; }
鎖的升級(jí)
除了上面所說(shuō),還要明白一個(gè)概念,鎖永遠(yuǎn)都會(huì)加載對(duì)象(或者說(shuō)堆中的資源上)上而不是線(xiàn)程上。所以升級(jí)過(guò)程為如下的步驟:
假設(shè):線(xiàn)程A訪(fǎng)問(wèn)對(duì)象資源A則此時(shí)立刻對(duì)資源A加上偏向鎖保證線(xiàn)程A訪(fǎng)問(wèn)資源A,且線(xiàn)程ID會(huì)被置為線(xiàn)程A。如果線(xiàn)程B來(lái)競(jìng)爭(zhēng),此時(shí)偏向鎖不會(huì)主動(dòng)釋放,線(xiàn)程B可以看到該鎖并知曉資源A已經(jīng)被線(xiàn)程A使用,于是檢查此時(shí)資源A是否被線(xiàn)程A使用(即線(xiàn)程A是否還存活),那么:
- 如果線(xiàn)程A已經(jīng)銷(xiāo)毀,則資源A分配給線(xiàn)程B,即分配偏向鎖給線(xiàn)程B并把線(xiàn)程ID置為線(xiàn)程B。
- 如果線(xiàn)程A沒(méi)有銷(xiāo)毀,則檢查線(xiàn)程A的操作棧,檢查資源A的使用情況:
- 如果線(xiàn)程A仍然在使用資源A,則把資源A升級(jí)為輕量級(jí)鎖,保證線(xiàn)程A的使用;
- 如果資源A已經(jīng)不再使用,已經(jīng)被線(xiàn)程A釋放了,則資源A分配給線(xiàn)程B,后續(xù)同1。
- 資源A已經(jīng)由于競(jìng)爭(zhēng),升級(jí)為輕量級(jí)鎖(此時(shí)資源A依然是被線(xiàn)程A使用),線(xiàn)程B仍然需要競(jìng)爭(zhēng)資源A。 因?yàn)檩p量級(jí)鎖會(huì)被認(rèn)為競(jìng)爭(zhēng)程度低,所以線(xiàn)程B被允許嘗試競(jìng)爭(zhēng),當(dāng)線(xiàn)程B再次發(fā)現(xiàn)資源A被輕量級(jí)鎖鎖住時(shí),線(xiàn)程B會(huì)進(jìn)入自旋狀態(tài)或者說(shuō)自旋鎖(即空轉(zhuǎn)一定時(shí)間,比如空的循環(huán)等等,等待資源A被釋放)。此時(shí)認(rèn)為資源A會(huì)很快被釋放,所以線(xiàn)程B進(jìn)入空轉(zhuǎn)等待資源A。
- 但是當(dāng)自旋超過(guò)一定次數(shù),或者線(xiàn)程C在線(xiàn)程B自旋時(shí),也來(lái)請(qǐng)求資源A,則資源A的輕量級(jí)鎖被升級(jí)為重量級(jí)鎖。由于重量級(jí)鎖能夠使得除了擁有當(dāng)前資源的線(xiàn)程以外的線(xiàn)程都阻塞,等待資源的釋放。所以此時(shí)資源A和線(xiàn)程A被重量級(jí)鎖綁在一起,不會(huì)釋放給線(xiàn)程B、線(xiàn)程C,因此線(xiàn)程B和線(xiàn)程C必須等待線(xiàn)程A釋放資源A以后在進(jìn)行新的競(jìng)爭(zhēng)
說(shuō)完這些,可能大家會(huì)有個(gè)疑問(wèn):為什么要進(jìn)行鎖的升級(jí)?假如很多線(xiàn)程都是處于輕量級(jí)上,都處于自旋狀態(tài),那么其中正在運(yùn)行一個(gè)線(xiàn)程以及釋放鎖了,但是此時(shí)有很多線(xiàn)程在自旋,都在相互競(jìng)爭(zhēng)??赡茉斐伤械木€(xiàn)程都會(huì)一直自旋下去,CPU空轉(zhuǎn)。為了阻止CPU空轉(zhuǎn),所以用重量級(jí)鎖把除了正在運(yùn)行的線(xiàn)程以外的所有線(xiàn)程阻塞,保證不會(huì)有新的競(jìng)爭(zhēng)。但是要注意的是鎖是由低到高的升級(jí),而且只會(huì)升級(jí),而不會(huì)降級(jí)。因?yàn)榧幢闶侵亓考?jí)鎖一旦占有線(xiàn)程釋放了,就直接是無(wú)鎖狀態(tài),不存在一層一層降級(jí)的過(guò)程。
鎖的重入性
當(dāng)有些時(shí)候必須對(duì)某個(gè)資源進(jìn)行二次加鎖也是可以的,比如下面這段代碼做了多重加鎖。
這樣也是可以的,之前的博客中說(shuō)過(guò)Java中每一個(gè)對(duì)象都有一個(gè)monitor對(duì)象,也就是一個(gè)監(jiān)視器,加鎖就是通過(guò)這個(gè)監(jiān)視器去加鎖。
當(dāng)某一個(gè)線(xiàn)程要占有這個(gè)對(duì)象的時(shí)候,先去檢查monitor對(duì)象的計(jì)數(shù)器是不是0,如果是0表示沒(méi)有線(xiàn)程占有這個(gè)對(duì)象,則成功占有這個(gè)對(duì)象,并且對(duì)這個(gè)對(duì)象的monitor計(jì)數(shù)器+1。
如果不為0,則說(shuō)明這個(gè)對(duì)象已經(jīng)被別的線(xiàn)程占用,那么就等待。當(dāng)線(xiàn)程釋放某個(gè)對(duì)象的占有時(shí)monitor的計(jì)數(shù)器-1。
注意這里是減一不是置0,也就是說(shuō)同一線(xiàn)程可以對(duì)同一對(duì)象進(jìn)行多次加鎖,進(jìn)行不斷地+1、+1,這個(gè)就是線(xiàn)程的重入性。
public synchronized void methodName3(){ synchronized (this){ // code ...... synchronized (this){ //加鎖的對(duì)象不同也可以,但是要避免對(duì)象交叉造成的死鎖 // code ...... synchronized (this){ // code ...... } } } }
悲觀鎖與樂(lè)觀鎖
悲觀鎖:一般指寫(xiě)操作比較多,包括增刪改,讀操作(查)比較少的鎖。也指不允許競(jìng)爭(zhēng)的鎖,遇到必須等待。
樂(lè)觀鎖:一般指讀(查)操作比較多,但是寫(xiě)操作比較少的鎖。也指可以競(jìng)爭(zhēng)的鎖,可以嘗試競(jìng)爭(zhēng)。 因此很多時(shí)候悲觀鎖一般要加鎖,樂(lè)觀鎖一般只用版本控制,讀取到最新的數(shù)據(jù)即可。只是一個(gè)概念,并不特地指某種所。
公平鎖與非公平鎖
公平鎖:其實(shí)就是排隊(duì),先來(lái)先得FIFO的邏輯,因此每個(gè)鎖都是公平的。
非公平鎖:就是采用一定的算法或者優(yōu)先度去對(duì)線(xiàn)程拿到鎖的順序進(jìn)行調(diào)整,因此每個(gè)鎖并不是公平獲取資源的。 這兩個(gè)鎖各有優(yōu)缺點(diǎn),使用誰(shuí)要看具體需求,比如緊急剎車(chē)作為一個(gè)功能來(lái)說(shuō),就必須用非公平鎖。
死鎖
當(dāng)由加鎖導(dǎo)致A線(xiàn)程等B線(xiàn)程釋放資源,B線(xiàn)程等A線(xiàn)程釋放資源,結(jié)果線(xiàn)程A和線(xiàn)程B都無(wú)法獲取資源導(dǎo)致,程序卡死在這里的情況就是死鎖。
總結(jié)
本篇延續(xù)著synchronized關(guān)鍵字的部分內(nèi)容,對(duì)鎖的概念、鎖的原理以及鎖的升級(jí),進(jìn)行了一個(gè)剖析,希望能夠?qū)Ω魑焕斫釰ava中的鎖有所幫助。
到此這篇關(guān)于Java中的鎖與鎖的狀態(tài)升級(jí)詳細(xì)解讀的文章就介紹到這了,更多相關(guān)Java鎖與鎖的狀態(tài)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java web實(shí)現(xiàn)簡(jiǎn)單聊天室
這篇文章主要為大家詳細(xì)介紹了java-web實(shí)現(xiàn)簡(jiǎn)單聊天室,含拍一拍功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11Java 通過(guò)反射變更String的值過(guò)程詳解
這篇文章主要介紹了Java 通過(guò)反射變更String的值過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10SSh結(jié)合Easyui實(shí)現(xiàn)Datagrid的分頁(yè)顯示
這篇文章主要為大家詳細(xì)介紹了SSh結(jié)合Easyui實(shí)現(xiàn)Datagrid的分頁(yè)顯示的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-06-06Spring?Data?Redis切換底層Jedis和Lettuce實(shí)現(xiàn)源碼解析
這篇文章主要為大家介紹了Spring?Data?Redis切換底層Jedis和Lettuce實(shí)現(xiàn)方法源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11分享Java多線(xiàn)程實(shí)現(xiàn)的四種方式
這篇文章主要介紹了分享Java多線(xiàn)程實(shí)現(xiàn)的四種方式,文章基于?Java的相關(guān)資料展開(kāi)多線(xiàn)程的詳細(xì)介紹,具有一的的參考價(jià)值,需要的小伙伴可以參考一下2022-05-05Spring Security跳轉(zhuǎn)頁(yè)面失敗問(wèn)題解決
這篇文章主要介紹了Spring Security跳轉(zhuǎn)頁(yè)面失敗問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01Spring?Boot統(tǒng)一處理全局異常的實(shí)戰(zhàn)教程
最近在做項(xiàng)目時(shí)需要對(duì)異常進(jìn)行全局統(tǒng)一處理,所以下面這篇文章主要給大家介紹了關(guān)于Spring?Boot統(tǒng)一處理全局異常的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-12-12springboot攔截器不攔截靜態(tài)資源,只攔截controller的實(shí)現(xiàn)方法
這篇文章主要介紹了springboot攔截器不攔截靜態(tài)資源,只攔截controller的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07elasticsearch索引index之put?mapping的設(shè)置分析
這篇文章主要為大家介紹了elasticsearch索引index之put?mapping的設(shè)置分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04