新聞中心
背后的原理
synchronized 關(guān)鍵字經(jīng)過編譯之后,會(huì)在同步塊的前后分別形成 monitorenter 和 monitorexit 這兩個(gè)字節(jié)碼指令,這兩個(gè)字節(jié)碼只需要一個(gè)指明一個(gè)要鎖定或解鎖的對(duì)象。如果 Java 程序中指明了對(duì)象參數(shù),那么就用這個(gè)對(duì)象作為鎖。

如果沒有指定,那么就根據(jù) synchronized 修飾的是實(shí)例方法還是類方法,去拿對(duì)應(yīng)的對(duì)象實(shí)例或 Class 對(duì)象來作為鎖對(duì)象。因此我們可以知道,synchronized 關(guān)鍵字實(shí)現(xiàn)線程同步的背后,其實(shí)是 Java 虛擬機(jī)規(guī)范對(duì)于 monitorenter 和 monitorexit 的定義。
在 Java 虛擬機(jī)規(guī)范對(duì) monitorenter 和 monitorexit 的行為描述中,有兩點(diǎn)需要特別注意。
synchronized 同步塊對(duì)同一條線程是可沖入的,也就是不會(huì)出現(xiàn)自己把自己鎖死的問題。
同步課在已進(jìn)入的線程執(zhí)行完之前,會(huì)阻塞后面其他線程的進(jìn)入。
synchronized 關(guān)鍵字在 JDK1.6 版本之前,是通過操作系統(tǒng)的 Mutex Lock 來實(shí)現(xiàn)同步的。而操作系統(tǒng)的 Mutex Lock 是操作系統(tǒng)級(jí)別的方法,需要切換到內(nèi)核態(tài)來執(zhí)行。這就需要從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài)中,因此我們說 synchronized 同步是重量級(jí)的操作。
鎖優(yōu)化
在 JDK1.6 版本中,HotSpot 虛擬機(jī)開發(fā)團(tuán)隊(duì)花了很大的精力去實(shí)現(xiàn)各種鎖優(yōu)化技術(shù),如:適應(yīng)性自旋、鎖消除、鎖粗話、偏向鎖、輕量級(jí)鎖等。其中最重要的是:自旋鎖、輕量級(jí)鎖、偏向鎖這三個(gè),我們重點(diǎn)講這三個(gè)鎖優(yōu)化。
自旋鎖與自適應(yīng)自旋
對(duì)于重量級(jí)的同步操作來說,最大的消耗其實(shí)是內(nèi)核態(tài)與用戶態(tài)的切換。很很多時(shí)候,對(duì)于共享數(shù)據(jù)的操作時(shí)間可能很短,比內(nèi)核態(tài)切換到用戶態(tài)這個(gè)耗時(shí)還短。
于是有人就想:如果有多個(gè)線程并發(fā)去獲取鎖的時(shí)候,如果能讓后面那個(gè)請(qǐng)求鎖的線程「稍等一下」,不放棄 CPU 的執(zhí)行時(shí)間,看看持有鎖的線程是否會(huì)很快釋放鎖。為了讓線程等待,我們只需讓線程執(zhí)行一個(gè)忙循環(huán)(自旋),這項(xiàng)技術(shù)就是所謂的自旋鎖。 從理論上來看,如果所有線程都很快地獲取鎖、釋放鎖,那么自旋鎖是可以帶來較大的性能提升的。自旋鎖在 JDK 1.4.2 中就已經(jīng)引入,默認(rèn)自旋 10 次。但自旋鎖默認(rèn)是關(guān)閉的,在 JDK 1.6 中才改為默認(rèn)開啟了。
自旋等待雖然避免了線程切換的開銷,但還是要占用處理器的時(shí)間。如果鎖被占用的時(shí)間段,自旋等待的效果就會(huì)非常好。但如果鎖被長時(shí)間占用,那么自旋的線程就會(huì)白白消耗處理器的資源,從而帶來性能上的浪費(fèi)。
為了解決特殊情況下自旋鎖的性能消耗問題,在 JDK1.6 的時(shí)候引入了自適應(yīng)的自旋鎖。 自適應(yīng)意味著自旋時(shí)間不再固定,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者狀態(tài)決定。如果在同一鎖對(duì)象上,自旋等待剛剛成功獲得過鎖,那么虛擬機(jī)認(rèn)為這次自旋也很有可能再次成功,進(jìn)而允許線程自旋更長時(shí)間,例如自旋 100 個(gè)循環(huán)。
但如果對(duì)于某個(gè)鎖,自旋很少成功獲得過。那虛擬機(jī)為了避免浪費(fèi) CPU 資源,有可能省略掉自旋過程。有了自旋鎖,隨著程序運(yùn)行和性能監(jiān)控信息的不斷完善,虛擬機(jī)對(duì)鎖的狀態(tài)預(yù)測就越準(zhǔn),虛擬機(jī)也會(huì)變得越來越聰明。
輕量級(jí)鎖
輕量級(jí)鎖是 JDK1.6 加入的新型鎖機(jī)制,名字中的「輕量級(jí)」是相對(duì)于操作系統(tǒng)互斥量這個(gè)重量級(jí)鎖而言的。輕量級(jí)鎖誕生的原因,是由于對(duì)于絕大部分的鎖而言,整個(gè)同步周期都不存在競爭。如果沒有競爭的話,那就沒必要使用重量級(jí)鎖了,于是就誕生了輕量級(jí)鎖來提高效率。
對(duì)于輕量級(jí)鎖來說,其同步的流程如下:
在代碼進(jìn)入同步塊的時(shí)候,如果此同步對(duì)象沒有被鎖定(鎖標(biāo)志位為 01 狀態(tài)),那么虛擬機(jī)會(huì)在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲(chǔ)鎖對(duì)象目前的 Mark Word 拷貝。
虛擬機(jī)將使用 CAS 操作嘗試將對(duì)象的 Mark Word 更新為指向 Lock Record 的指針。如果更新動(dòng)作成功了,那么線程就泳衣了該對(duì)象的鎖,并且對(duì)象 Mark Word 的鎖標(biāo)志位就變成了 00,表示此對(duì)象處于輕量級(jí)鎖定狀態(tài)。
簡單地說,輕量級(jí)鎖的同步流程可以總結(jié)為:使用 CAS 操作,在線程棧幀與鎖對(duì)象建立雙向的指針。
在沒有線程競爭的情況下,輕量級(jí)鎖使用 CAS 自旋操作避免了使用互斥量的開銷,提高了效率。但如果存在鎖競爭,除了互斥量的開銷外,還額外發(fā)生了 CAS 操作。因此在有競爭的情況下,輕量級(jí)鎖會(huì)比傳統(tǒng)的重量級(jí)鎖更慢。
偏向鎖
偏向鎖是 JDK1.6 中引入的一項(xiàng)優(yōu)化,它的意思是這個(gè)鎖會(huì)偏向于第一個(gè)獲得它的線程。如果在接下來的執(zhí)行過程中,該鎖沒有被其他線程獲取,則持有偏向鎖的線程將永遠(yuǎn)不需要再進(jìn)行同步。 對(duì)于偏向鎖來說,其同步流程如下所示:
- 假設(shè)當(dāng)前虛擬機(jī)啟動(dòng)了偏向鎖,那么當(dāng)鎖對(duì)象第一次被線程獲取的時(shí)候,虛擬機(jī)將會(huì)把對(duì)象的鎖標(biāo)志位設(shè)置為 01,偏向鎖位設(shè)置為 1。同時(shí)使用 CAS 操作將線程 ID 記錄在對(duì)象的 MarkWord 之中。如果 CAS 操作成功,那么持有偏向鎖的線程進(jìn)入鎖對(duì)應(yīng)的同步塊時(shí),虛擬機(jī)將不再進(jìn)行任何同步操作。
- 當(dāng)有另外一個(gè)線程嘗試去獲取這個(gè)鎖時(shí),根據(jù)鎖對(duì)象目前是否處于鎖定狀態(tài),將其恢復(fù)到未鎖定(01)或輕量級(jí)鎖定(00)狀態(tài)。隨后的同步操作,就向上面介紹的輕量級(jí)鎖那樣執(zhí)行。
可以看到偏向鎖還是需要做一些 CAS 操作,但是對(duì)比起輕量級(jí)鎖來說,其要設(shè)置的內(nèi)容大大減少了,因此也提高了一些效率。
偏向鎖可以提高帶有同步但無競爭的程序性能。 它同樣是一個(gè)帶有效益權(quán)衡(Trade Off)性質(zhì)的優(yōu)化,也就是說,它并不一定總是對(duì)程序運(yùn)行有利,如果程序中大多數(shù)的鎖總是被多個(gè)不同的線程訪問,那偏向模式就是多余的。
優(yōu)化后的鎖獲取流程
經(jīng)過 JDK1.6 的優(yōu)化,synchronized 同步機(jī)制的流程變成了:
- 首先,synchronized 會(huì)嘗試使用偏向鎖的方式去競爭鎖資源,如果能夠競爭到偏向鎖,表示加鎖成功直接返回。
- 如果競爭鎖失敗,說明當(dāng)前鎖已經(jīng)偏向了其他線程。需要將鎖升級(jí)到輕量級(jí)鎖,在輕量級(jí)鎖狀態(tài)下,競爭鎖的線程根據(jù)自適應(yīng)自旋次數(shù)去嘗試搶占鎖資源。
- 如果在輕量級(jí)鎖狀態(tài)下還是沒有競爭到鎖,就只能升級(jí)到重量級(jí)鎖。在重量級(jí)鎖狀態(tài)下,沒有競爭到鎖的線程就會(huì)被阻塞。處于鎖等待狀態(tài)的線程需要等待獲得鎖的線程來觸發(fā)喚醒。
上面的鎖獲取流程,可以用如下的示意圖來表示:
Java 對(duì)象鎖競爭流程
總結(jié)
本文首先簡單講解了 synchronized 關(guān)鍵字實(shí)現(xiàn)同步的原理,其實(shí)是通過 Java 虛擬機(jī)規(guī)范對(duì)于 monitorenter 和 monitorexit 的支持,從而使得 synchronized 能夠?qū)崿F(xiàn)同步。而 synchronized 同步本質(zhì)上是通過操作系統(tǒng)的 mutex 鎖來實(shí)現(xiàn)的。由于操作操作系統(tǒng) mutex 鎖太過于消耗資源,因此在 JDK1.6 后 HotSpot 虛擬機(jī)做了一系列的鎖優(yōu)化,其中最重要的便是:自旋鎖、輕量級(jí)鎖、偏向鎖。這三個(gè)鎖的誕生原因,以及提升的點(diǎn)如下表所示。
|
現(xiàn)狀 |
鎖名稱 |
收益 |
使用場景 |
|
大多數(shù)情況下,等待鎖的時(shí)間比操作系統(tǒng) mutex 短得多 |
自旋鎖 |
減少內(nèi)核態(tài)與用戶態(tài)切換的開銷 |
線程獲取鎖時(shí)間較短的情況 |
|
大多數(shù)情況下,鎖同步期間沒有線程競爭 |
輕量級(jí)鎖 |
與自旋鎖相比,減少了自旋時(shí)間 |
沒有線程競爭鎖 |
|
大多數(shù)情況下,鎖同步期間沒有線程競爭 |
偏向鎖 |
與輕量級(jí)鎖相比,減少了多余的對(duì)象復(fù)制操作 |
沒有線程競爭鎖 |
從上面表格可以看到,自旋鎖、輕量級(jí)鎖、偏向鎖,他們的優(yōu)化是逐漸深入的。
- 對(duì)于重量級(jí)鎖來說,自旋鎖減少了互斥量的內(nèi)核、用戶態(tài)切換開銷。
- 對(duì)于自旋鎖來說,輕量級(jí)鎖減少了自旋等待的時(shí)間。
- 對(duì)于輕量級(jí)鎖來說,偏向于減少了多余的對(duì)象復(fù)制操作。
網(wǎng)頁標(biāo)題:深入理解Synchronized的鎖優(yōu)化
URL標(biāo)題:http://fisionsoft.com.cn/article/dpddpip.html


咨詢
建站咨詢
