自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入理解 Synchronized 的鎖優(yōu)化

開(kāi)發(fā) 前端
本文首先簡(jiǎn)單講解了 synchronized 關(guān)鍵字實(shí)現(xiàn)同步的原理,其實(shí)是通過(guò) Java 虛擬機(jī)規(guī)范對(duì)于 monitorenter 和 monitorexit 的支持,從而使得 synchronized 能夠?qū)崿F(xiàn)同步。

我們都知道 synchronized 關(guān)鍵字能實(shí)現(xiàn)線程安全,但是你知道這背后的原理是什么嗎?今天我們就來(lái)講一講 synchronized 實(shí)現(xiàn)線程同步背后的原因,以及相關(guān)的鎖優(yōu)化策略吧。

背后的原理

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

如果沒(méi)有指定,那么就根據(jù) synchronized 修飾的是實(shí)例方法還是類方法,去拿對(duì)應(yīng)的對(duì)象實(shí)例或 Class 對(duì)象來(lái)作為鎖對(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)自己把自己鎖死的問(wèn)題。

同步課在已進(jìn)入的線程執(zhí)行完之前,會(huì)阻塞后面其他線程的進(jìn)入。

synchronized 關(guān)鍵字在 JDK1.6 版本之前,是通過(guò)操作系統(tǒng)的 Mutex Lock 來(lái)實(shí)現(xiàn)同步的。而操作系統(tǒng)的 Mutex Lock 是操作系統(tǒng)級(jí)別的方法,需要切換到內(nèi)核態(tài)來(lái)執(zhí)行。這就需要從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài)中,因此我們說(shuō) synchronized 同步是重量級(jí)的操作。

鎖優(yōu)化

在 JDK1.6 版本中,HotSpot 虛擬機(jī)開(kāi)發(fā)團(tuán)隊(duì)花了很大的精力去實(shí)現(xiàn)各種鎖優(yōu)化技術(shù),如:適應(yīng)性自旋、鎖消除、鎖粗話、偏向鎖、輕量級(jí)鎖等。其中最重要的是:自旋鎖、輕量級(jí)鎖、偏向鎖這三個(gè),我們重點(diǎn)講這三個(gè)鎖優(yōu)化。

自旋鎖與自適應(yīng)自旋

對(duì)于重量級(jí)的同步操作來(lái)說(shuō),最大的消耗其實(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ù)就是所謂的自旋鎖。 從理論上來(lái)看,如果所有線程都很快地獲取鎖、釋放鎖,那么自旋鎖是可以帶來(lái)較大的性能提升的。自旋鎖在 JDK 1.4.2 中就已經(jīng)引入,默認(rèn)自旋 10 次。但自旋鎖默認(rèn)是關(guān)閉的,在 JDK 1.6 中才改為默認(rèn)開(kāi)啟了。

自旋等待雖然避免了線程切換的開(kāi)銷(xiāo),但還是要占用處理器的時(shí)間。如果鎖被占用的時(shí)間段,自旋等待的效果就會(huì)非常好。但如果鎖被長(zhǎng)時(shí)間占用,那么自旋的線程就會(huì)白白消耗處理器的資源,從而帶來(lái)性能上的浪費(fèi)。

為了解決特殊情況下自旋鎖的性能消耗問(wèn)題,在 JDK1.6 的時(shí)候引入了自適應(yīng)的自旋鎖。 自適應(yīng)意味著自旋時(shí)間不再固定,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者狀態(tài)決定。如果在同一鎖對(duì)象上,自旋等待剛剛成功獲得過(guò)鎖,那么虛擬機(jī)認(rèn)為這次自旋也很有可能再次成功,進(jìn)而允許線程自旋更長(zhǎng)時(shí)間,例如自旋 100 個(gè)循環(huán)。

但如果對(duì)于某個(gè)鎖,自旋很少成功獲得過(guò)。那虛擬機(jī)為了避免浪費(fèi) CPU 資源,有可能省略掉自旋過(guò)程。有了自旋鎖,隨著程序運(yùn)行和性能監(jiān)控信息的不斷完善,虛擬機(jī)對(duì)鎖的狀態(tài)預(yù)測(cè)就越準(zhǔn),虛擬機(jī)也會(huì)變得越來(lái)越聰明。

輕量級(jí)鎖

輕量級(jí)鎖是 JDK1.6 加入的新型鎖機(jī)制,名字中的「輕量級(jí)」是相對(duì)于操作系統(tǒng)互斥量這個(gè)重量級(jí)鎖而言的。輕量級(jí)鎖誕生的原因,是由于對(duì)于絕大部分的鎖而言,整個(gè)同步周期都不存在競(jìng)爭(zhēng)。如果沒(méi)有競(jìng)爭(zhēng)的話,那就沒(méi)必要使用重量級(jí)鎖了,于是就誕生了輕量級(jí)鎖來(lái)提高效率。

對(duì)于輕量級(jí)鎖來(lái)說(shuō),其同步的流程如下:

在代碼進(jìn)入同步塊的時(shí)候,如果此同步對(duì)象沒(méi)有被鎖定(鎖標(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)。

簡(jiǎn)單地說(shuō),輕量級(jí)鎖的同步流程可以總結(jié)為:使用 CAS 操作,在線程棧幀與鎖對(duì)象建立雙向的指針。

在沒(méi)有線程競(jìng)爭(zhēng)的情況下,輕量級(jí)鎖使用 CAS 自旋操作避免了使用互斥量的開(kāi)銷(xiāo),提高了效率。但如果存在鎖競(jìng)爭(zhēng),除了互斥量的開(kāi)銷(xiāo)外,還額外發(fā)生了 CAS 操作。因此在有競(jìng)爭(zhēng)的情況下,輕量級(jí)鎖會(huì)比傳統(tǒng)的重量級(jí)鎖更慢。

偏向鎖

偏向鎖是 JDK1.6 中引入的一項(xiàng)優(yōu)化,它的意思是這個(gè)鎖會(huì)偏向于第一個(gè)獲得它的線程。如果在接下來(lái)的執(zhí)行過(guò)程中,該鎖沒(méi)有被其他線程獲取,則持有偏向鎖的線程將永遠(yuǎn)不需要再進(jìn)行同步。 對(duì)于偏向鎖來(lái)說(shuō),其同步流程如下所示:

  • 假設(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í)鎖來(lái)說(shuō),其要設(shè)置的內(nèi)容大大減少了,因此也提高了一些效率。

偏向鎖可以提高帶有同步但無(wú)競(jìng)爭(zhēng)的程序性能。 它同樣是一個(gè)帶有效益權(quán)衡(Trade Off)性質(zhì)的優(yōu)化,也就是說(shuō),它并不一定總是對(duì)程序運(yùn)行有利,如果程序中大多數(shù)的鎖總是被多個(gè)不同的線程訪問(wèn),那偏向模式就是多余的。

優(yōu)化后的鎖獲取流程

經(jīng)過(guò) JDK1.6 的優(yōu)化,synchronized 同步機(jī)制的流程變成了:

  • 首先,synchronized 會(huì)嘗試使用偏向鎖的方式去競(jìng)爭(zhēng)鎖資源,如果能夠競(jìng)爭(zhēng)到偏向鎖,表示加鎖成功直接返回。
  • 如果競(jìng)爭(zhēng)鎖失敗,說(shuō)明當(dāng)前鎖已經(jīng)偏向了其他線程。需要將鎖升級(jí)到輕量級(jí)鎖,在輕量級(jí)鎖狀態(tài)下,競(jìng)爭(zhēng)鎖的線程根據(jù)自適應(yīng)自旋次數(shù)去嘗試搶占鎖資源。
  • 如果在輕量級(jí)鎖狀態(tài)下還是沒(méi)有競(jìng)爭(zhēng)到鎖,就只能升級(jí)到重量級(jí)鎖。在重量級(jí)鎖狀態(tài)下,沒(méi)有競(jìng)爭(zhēng)到鎖的線程就會(huì)被阻塞。處于鎖等待狀態(tài)的線程需要等待獲得鎖的線程來(lái)觸發(fā)喚醒。

上面的鎖獲取流程,可以用如下的示意圖來(lái)表示:

圖片

Java 對(duì)象鎖競(jìng)爭(zhēng)流程

總結(jié)

本文首先簡(jiǎn)單講解了 synchronized 關(guān)鍵字實(shí)現(xiàn)同步的原理,其實(shí)是通過(guò) Java 虛擬機(jī)規(guī)范對(duì)于 monitorenter 和 monitorexit 的支持,從而使得 synchronized 能夠?qū)崿F(xiàn)同步。而 synchronized 同步本質(zhì)上是通過(guò)操作系統(tǒng)的 mutex 鎖來(lái)實(shí)現(xiàn)的。由于操作操作系統(tǒng) mutex 鎖太過(guò)于消耗資源,因此在 JDK1.6 后 HotSpot 虛擬機(jī)做了一系列的鎖優(yōu)化,其中最重要的便是:自旋鎖、輕量級(jí)鎖、偏向鎖。這三個(gè)鎖的誕生原因,以及提升的點(diǎn)如下表所示。

現(xiàn)狀

鎖名稱

收益

使用場(chǎng)景

大多數(shù)情況下,等待鎖的時(shí)間比操作系統(tǒng) mutex 短得多

自旋鎖

減少內(nèi)核態(tài)與用戶態(tài)切換的開(kāi)銷(xiāo)

線程獲取鎖時(shí)間較短的情況

大多數(shù)情況下,鎖同步期間沒(méi)有線程競(jìng)爭(zhēng)

輕量級(jí)鎖

與自旋鎖相比,減少了自旋時(shí)間

沒(méi)有線程競(jìng)爭(zhēng)鎖

大多數(shù)情況下,鎖同步期間沒(méi)有線程競(jìng)爭(zhēng)

偏向鎖

與輕量級(jí)鎖相比,減少了多余的對(duì)象復(fù)制操作

沒(méi)有線程競(jìng)爭(zhēng)鎖

從上面表格可以看到,自旋鎖、輕量級(jí)鎖、偏向鎖,他們的優(yōu)化是逐漸深入的。

  • 對(duì)于重量級(jí)鎖來(lái)說(shuō),自旋鎖減少了互斥量的內(nèi)核、用戶態(tài)切換開(kāi)銷(xiāo)。
  • 對(duì)于自旋鎖來(lái)說(shuō),輕量級(jí)鎖減少了自旋等待的時(shí)間。
  • 對(duì)于輕量級(jí)鎖來(lái)說(shuō),偏向于減少了多余的對(duì)象復(fù)制操作。
責(zé)任編輯:武曉燕 來(lái)源: 陳樹(shù)義
相關(guān)推薦

2021-05-27 11:30:54

SynchronizeJava代碼

2020-11-13 08:42:24

Synchronize

2021-07-26 07:47:37

無(wú)鎖編程CPU

2023-10-13 13:30:00

MySQL鎖機(jī)制

2023-10-31 10:51:56

MySQLMVCC并發(fā)性

2018-03-22 18:30:22

數(shù)據(jù)庫(kù)MySQL并發(fā)控制

2010-06-01 15:25:27

JavaCLASSPATH

2016-12-08 15:36:59

HashMap數(shù)據(jù)結(jié)構(gòu)hash函數(shù)

2020-07-21 08:26:08

SpringSecurity過(guò)濾器

2014-07-15 17:17:31

AdapterAndroid

2012-11-22 10:11:16

LispLisp教程

2013-09-22 14:57:19

AtWood

2023-10-19 11:12:15

Netty代碼

2021-02-17 11:25:33

前端JavaScriptthis

2009-09-25 09:14:35

Hibernate日志

2024-03-15 09:44:17

WPFDispatcherUI線程

2020-09-23 10:00:26

Redis數(shù)據(jù)庫(kù)命令

2017-01-10 08:48:21

2017-08-15 13:05:58

Serverless架構(gòu)開(kāi)發(fā)運(yùn)維

2024-02-21 21:14:20

編程語(yǔ)言開(kāi)發(fā)Golang
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)