对 java 同步锁 以及 级别升级的 理解
首先簡單說下先偏向鎖、輕量級鎖、重量級鎖三者各自的應(yīng)用場景:
- 偏向鎖:只有一個線程進入臨界區(qū);
- 輕量級鎖:多個線程交替進入臨界區(qū);
- 重量級鎖:多個線程同時進入臨界區(qū)。
還要明確的是,偏向鎖、輕量級鎖都是JVM引入的鎖優(yōu)化手段,目的是降低線程同步的開銷。比如以下的同步代碼塊:
synchronized (lockObject) {// do something }上述同步代碼塊中存在一個臨界區(qū),假設(shè)當(dāng)前存在Thread#1和Thread#2這兩個用戶線程,分三種情況來討論:
- 情況一:只有Thread#1會進入臨界區(qū);
- 情況二:Thread#1和Thread#2交替進入臨界區(qū);
- 情況三:Thread#1和Thread#2同時進入臨界區(qū)。
上述的情況一是偏向鎖的適用場景,此時當(dāng)Thread#1進入臨界區(qū)時,JVM會將lockObject的對象頭Mark Word的鎖標(biāo)志位設(shè)為“01”,同時會用CAS操作把Thread#1的線程ID記錄到Mark Word中,此時進入偏向模式。所謂“偏向”,指的是這個鎖會偏向于Thread#1,若接下來沒有其他線程進入臨界區(qū),則Thread#1再出入臨界區(qū)無需再執(zhí)行任何同步操作。也就是說,若只有Thread#1會進入臨界區(qū),實際上只有Thread#1初次進入臨界區(qū)時需要執(zhí)行CAS操作,以后再出入臨界區(qū)都不會有同步操作帶來的開銷。
然而情況一是一個比較理想的情況,更多時候Thread#2也會嘗試進入臨界區(qū)。若Thread#2嘗試進入時Thread#1已退出臨界區(qū),即此時lockObject處于未鎖定狀態(tài),這時說明偏向鎖上發(fā)生了競爭(對應(yīng)情況二),此時會撤銷偏向,Mark Word中不再存放偏向線程ID,而是存放hashCode和GC分代年齡,同時鎖標(biāo)識位變?yōu)椤?1”(表示未鎖定),這時Thread#2會獲取lockObject的輕量級鎖。因為此時Thread#1和Thread#2交替進入臨界區(qū),所以偏向鎖無法滿足需求,需要膨脹到輕量級鎖。
再說輕量級鎖什么時候會膨脹到重量級鎖。若一直是Thread#1和Thread#2交替進入臨界區(qū),那么沒有問題,輕量鎖hold住。一旦在輕量級鎖上發(fā)生競爭,即出現(xiàn)“Thread#1和Thread#2同時進入臨界區(qū)”的情況,輕量級鎖就hold不住了。 (根本原因是輕量級鎖沒有足夠的空間存儲額外狀態(tài),此時若不膨脹為重量級鎖,則所有等待輕量鎖的線程只能自旋,可能會損失很多CPU時間)
MarkWord:
鎖存在Java對象頭里。如果對象是數(shù)組類型,則虛擬機用3個Word(字寬)存儲對象頭,如果對象是非數(shù)組類型,則用2字寬存儲對象頭。在32位虛擬機中,一字寬等于四字節(jié),即32bit。
長度 | 內(nèi)容 | 說明 |
32/64bit | Mark Word | 存儲對象的hashCode或鎖信息等。 |
32/64bit | Class Metadata Address | 存儲到對象類型數(shù)據(jù)的指針 |
32/64bit | Array length | 數(shù)組的長度(如果當(dāng)前對象是數(shù)組) |
Java對象頭里的Mark Word里默認(rèn)存儲對象的HashCode,分代年齡和鎖標(biāo)記位。32位JVM的Mark Word的默認(rèn)存儲結(jié)構(gòu)如下:
? | 25 bit | 4bit | 1bit 是否是偏向鎖 | 2bit 鎖標(biāo)志位 |
無鎖狀態(tài) | 對象的hashCode | 對象分代年齡 | 0 | 01 |
在運行期間Mark Word里存儲的數(shù)據(jù)會隨著鎖標(biāo)志位的變化而變化。Mark Word可能變化為存儲以下4種數(shù)據(jù):
鎖狀態(tài) | 25 bit | 4bit | 1bit | 2bit | |
23bit | 2bit | 是否是偏向鎖 | 鎖標(biāo)志位 | ||
輕量級鎖 | 指向棧中鎖記錄的指針 | 00 | |||
重量級鎖 | 指向互斥量(重量級鎖)的指針 | 10 | |||
GC標(biāo)記 | 空 | 11 | |||
偏向鎖 | 線程ID | Epoch | 對象分代年齡 | 1 | 01 |
在64位虛擬機下,Mark Word是64bit大小的,其存儲結(jié)構(gòu)如下:?
鎖狀態(tài) | 25bit | 31bit | 1bit | 4bit | 1bit | 2bit |
? | ? | cms_free | 分代年齡 | 偏向鎖 | 鎖標(biāo)志位 | |
無鎖 | unused | hashCode | ? | ? | 0 | 01 |
偏向鎖 | ThreadID(54bit) Epoch(2bit) | ? | ? | 1 | 01 |
上面是最簡單的說法或者理解,但是實際上有很多的切換細節(jié)設(shè)計性能優(yōu)化問題。
偏向所鎖,輕量級鎖都是樂觀鎖,重量級鎖是悲觀鎖。? ? ?
一個對象剛開始實例化的時候,沒有任何線程來訪問它的時候。它是可偏向的,意味著,它現(xiàn)在認(rèn)為只可能有一個線程來訪問它,所以當(dāng)?shù)谝粋€線程來訪問它的時候,它會偏向這個線程,此時,對象持有偏向鎖。偏向第一個線程,這個線程在修改對象頭成為偏向鎖的時候使用CAS操作,并將對象頭中的ThreadID改成自己的ID,之后再次訪問這個對象時,只需要對比ID,不需要再使用CAS在進行操作。(偏向鎖只進行一次cas操作)
一旦有第二個線程訪問這個對象,因為偏向鎖不會主動釋放,所以第二個線程可以看到對象時偏向狀態(tài),這時表明在這個對象上已經(jīng)存在競爭了,檢查原來持有該對象鎖的線程是否依然存活,如果掛了,則可以將對象變?yōu)闊o鎖狀態(tài),然后重新偏向新的線程,如果原來的線程依然存活,則馬上執(zhí)行那個線程的操作棧,檢查該對象的使用情況,如果仍然需要持有偏向鎖,則偏向鎖升級為輕量級鎖,(偏向鎖就是這個時候升級為輕量級鎖的)。
如果不存在使用了,則可以將對象回復(fù)成無鎖狀態(tài),然后重新偏向。 輕量級鎖認(rèn)為競爭存在,但是競爭的程度很輕,一般兩個線程對于同一個鎖的操作都會錯開,或者說稍微等待一下(自旋),另一個線程就會釋放鎖。 但是當(dāng)自旋超過一定的次數(shù),或者一個線程在持有鎖,一個在自旋,又有第三個來訪時,輕量級鎖膨脹為重量級鎖,重量級鎖使除了擁有鎖的線程以外的線程都阻塞,防止CPU空轉(zhuǎn)。
為什么此時升級為重量級鎖呢,將所有競爭線程掛起(重量級鎖,是jvm 借助系統(tǒng)級的互斥鎖)??假如很多線程都是處于輕量級上,都處于自旋狀態(tài),那么其中正在運行一個線程以及釋放鎖了,但是此時有很多線程在自旋,都在相互競爭。可能造成所有的線程都會一直自旋下去,cpu空轉(zhuǎn)。
總結(jié)
以上是生活随笔為你收集整理的对 java 同步锁 以及 级别升级的 理解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 腾讯云宣布多款核心产品降价:最高降幅达
- 下一篇: win10系统定时自动切换深色模式