欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java實(shí)現(xiàn)synchronized鎖同步機(jī)制

 更新時(shí)間:2021年11月04日 11:11:55   作者:看山  
synchronized是java內(nèi)置的同步鎖實(shí)現(xiàn),本文就詳細(xì)的介紹一下Java實(shí)現(xiàn)synchronized鎖同步機(jī)制,具有一定的參考價(jià)值,感興趣的可以了解一下

synchronized 是 java 內(nèi)置的同步鎖實(shí)現(xiàn),一個(gè)關(guān)鍵字實(shí)現(xiàn)對(duì)共享資源的鎖定。synchronized 有 3 種使用場(chǎng)景,場(chǎng)景不同,加鎖對(duì)象也不同:

  • 普通方法:鎖對(duì)象是當(dāng)前實(shí)例對(duì)象
  • 靜態(tài)方法:鎖對(duì)象是類的 Class 對(duì)象
  • 方法塊:鎖對(duì)象是 synchronized 括號(hào)中的對(duì)象

synchronized 實(shí)現(xiàn)原理

synchronized 是通過(guò)進(jìn)入和退出 Monitor 對(duì)象實(shí)現(xiàn)鎖機(jī)制,代碼塊通過(guò)一對(duì) monitorenter/monitorexit 指令實(shí)現(xiàn)。在編譯后,monitorenter 指令插入到同步代碼塊的開始位置,monitorexit 指令插入到方法結(jié)束和異常處,JVM 要保證 monitorenter 和 monitorexit 成對(duì)出現(xiàn)。任何對(duì)象都有一個(gè) Monitor 與之關(guān)聯(lián),當(dāng)且僅當(dāng)一個(gè) Monitor 被持有后,它將處于鎖狀態(tài)。

在執(zhí)行 monitorenter 時(shí),首先嘗試獲取對(duì)象的鎖,如果對(duì)象沒有被鎖定或者當(dāng)前線程持有鎖,鎖的計(jì)數(shù)器加 1;相應(yīng)的,在執(zhí)行 monitorexit 指令時(shí),將鎖的計(jì)數(shù)器減 1。當(dāng)計(jì)數(shù)器減到 0 時(shí),鎖釋放。如果在 monitorenter 獲取鎖失敗,當(dāng)前線程會(huì)被阻塞,直到對(duì)象鎖被釋放。

在 JDK6 之前,Monitor 的實(shí)現(xiàn)是依靠操作系統(tǒng)內(nèi)部的互斥鎖實(shí)現(xiàn)(一般使用的是 Mutex Lock 實(shí)現(xiàn)),線程阻塞會(huì)進(jìn)行用戶態(tài)和內(nèi)核態(tài)的切換,所以同步操作是一個(gè)無(wú)差別的重量級(jí)鎖。

后來(lái),JDK 對(duì) synchronized 進(jìn)行升級(jí),為了避免線程阻塞時(shí)在用戶態(tài)與內(nèi)核態(tài)之間切換線程,會(huì)在操作系統(tǒng)阻塞線程前,加入自旋操作。然后還實(shí)現(xiàn) 3 種不同的 Monitor:偏向鎖(Biased Locking)、輕量級(jí)鎖(Lightweight Locking)、重量級(jí)鎖。在 JDK6 之后,synchronized 的性能得到很大的提升,相比于 ReentrantLock 而言,性能并不差,只不過(guò) ReentrantLock 使用起來(lái)更加靈活。

適應(yīng)性自旋(Adaptive Spinning)

synchronized 對(duì)性能影響最大的是阻塞的實(shí)現(xiàn),掛起線程和恢復(fù)線程都需要操作系統(tǒng)幫助完成,需要從用戶態(tài)轉(zhuǎn)到內(nèi)核態(tài),狀態(tài)轉(zhuǎn)換需要耗費(fèi)很多 CPU 時(shí)間。

在我們大多數(shù)的應(yīng)用中,共享數(shù)據(jù)的鎖定狀態(tài)只會(huì)持續(xù)很短的一段時(shí)間,為了這段時(shí)間掛起和回復(fù)線程消耗的時(shí)間不值得。而且,現(xiàn)在大多數(shù)的處理器都是多核處理器,如果讓后一個(gè)線程再等一會(huì),不釋放 CPU,等前一個(gè)釋放鎖,后一個(gè)線程立馬獲取鎖執(zhí)行任務(wù)就行。這就是所謂的自旋,讓線程執(zhí)行一個(gè)忙循環(huán),自己在原地轉(zhuǎn)一會(huì),每轉(zhuǎn)一圈看看鎖釋放沒有,釋放了直接獲取鎖,沒有釋放就再轉(zhuǎn)一圈。

自旋鎖是在 JDK 1.4.2 引入(使用-XX:+UseSpinning參數(shù)打開),JDK 1.6 默認(rèn)打開。自旋鎖不能代替阻塞,因?yàn)樽孕却m然避免了線程切換的開銷,但是它要占用 CPU 時(shí)間,如果鎖占用時(shí)間短,自旋等待效果挺好,反之,則是性能浪費(fèi)。所以在 JDK 1.6 中引入了自適應(yīng)自旋鎖:如果同一個(gè)鎖對(duì)象,自旋等待剛成功,且持有鎖的線程正在運(yùn)行,那本次自旋很有可能成功,會(huì)允許自旋等待持續(xù)時(shí)間長(zhǎng)一些。反之,如果對(duì)于某個(gè)鎖,自旋很少成功,那之后很有可能直接省略自旋過(guò)程,避免浪費(fèi) CPU 資源。

鎖升級(jí)

Java 對(duì)象頭

synchronized 用的鎖存在于 Java 對(duì)象頭里,對(duì)象頭里的 Mark Word 里存儲(chǔ)的數(shù)據(jù)會(huì)隨標(biāo)志位的變化而變化,變化如下:

Java 對(duì)象頭 Mark Word

偏向鎖(Biased Locking)

大多數(shù)情況下,鎖不僅不存在多線程競(jìng)爭(zhēng),而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價(jià)更低,引入偏向鎖。
當(dāng)一個(gè)線程訪問(wèn)同步塊并獲取鎖時(shí),會(huì)在對(duì)象頭和棧幀中的鎖記錄里存儲(chǔ)鎖偏向的線程 ID,以后該線程在進(jìn)入和退出同步塊時(shí)不需要進(jìn)行 CAS 操作來(lái)加鎖和解鎖,只需簡(jiǎn)單地測(cè)試一下對(duì)象頭的 Mark Word 里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖。引入偏向鎖是為了在無(wú)多線程競(jìng)爭(zhēng)的情況下盡量減少不必要的輕量級(jí)鎖執(zhí)行路徑,因?yàn)檩p量級(jí)鎖的獲取及釋放依賴多次 CAS 原子指令,而偏向鎖只需要在置換 ThreadID 的時(shí)候依賴一次 CAS 原子指令(由于一旦出現(xiàn)多線程競(jìng)爭(zhēng)的情況就必須撤銷偏向鎖,所以偏向鎖的撤銷操作的性能損耗必須小于節(jié)省下來(lái)的 CAS 原子指令的性能消耗)。

偏向鎖獲取

  • 當(dāng)鎖對(duì)象第一次被線程獲取時(shí),對(duì)象頭的標(biāo)志位設(shè)為 01,偏向模式設(shè)為 1,表示進(jìn)入偏向模式。
  • 測(cè)試線程 ID 是否指向當(dāng)前線程,如果是,執(zhí)行同步代碼塊,如果否,進(jìn)入 3
  • 使用 CAS 操作把獲得到的這個(gè)鎖的線程 ID 記錄在對(duì)象的 Mark Word 中。如果成功,執(zhí)行同步代碼塊,如果失敗,說(shuō)明存在過(guò)其他線程持有鎖對(duì)象的偏向鎖,開始嘗試當(dāng)前線程獲取偏向鎖
  • 當(dāng)?shù)竭_(dá)全局安全點(diǎn)時(shí)(沒有字節(jié)碼正在執(zhí)行),會(huì)暫停擁有偏向鎖的線程,檢查線程狀態(tài)。如果線程已經(jīng)結(jié)束,則將對(duì)象頭設(shè)置成無(wú)鎖狀態(tài)(標(biāo)志位為“01”),然后重新偏向新的線程;如果線程仍然活著,撤銷偏向鎖后升級(jí)到輕量級(jí)鎖狀態(tài)(標(biāo)志位為“00”),此時(shí)輕量級(jí)鎖由原持有偏向鎖的線程持有,繼續(xù)執(zhí)行其同步代碼,而正在競(jìng)爭(zhēng)的線程會(huì)進(jìn)入自旋等待獲得該輕量級(jí)鎖。

偏向鎖釋放

偏向鎖的釋放采用的是惰性釋放機(jī)制:只有等到競(jìng)爭(zhēng)出現(xiàn),才釋放偏向鎖。釋放過(guò)程就是上面說(shuō)的第 4 步,這里不再贅述。

關(guān)閉偏向鎖

偏斜鎖并不適合所有應(yīng)用場(chǎng)景,撤銷操作(revoke)是比較重的行為,只有當(dāng)存在較多不會(huì)真正競(jìng)爭(zhēng)的同步塊時(shí),才能體現(xiàn)出明顯改善。實(shí)踐中對(duì)于偏斜鎖的一直是有爭(zhēng)議的,有人甚至認(rèn)為,當(dāng)你需要大量使用并發(fā)類庫(kù)時(shí),往往意味著你不需要偏斜鎖。

所以如果你確定應(yīng)用程序里的鎖通常情況下處于競(jìng)爭(zhēng)狀態(tài),可以通過(guò) JVM 參數(shù)關(guān)閉偏向鎖:-XX:-UseBiasedLocking=false,那么程序默認(rèn)會(huì)進(jìn)入輕量級(jí)鎖狀態(tài)。

輕量級(jí)鎖(Lightweight Locking)

輕量級(jí)鎖不是用來(lái)代替重量級(jí)鎖的,它的初衷是在沒有多線程競(jìng)爭(zhēng)的前提下,減少傳統(tǒng)的重量級(jí)鎖使用操作系統(tǒng)互斥量產(chǎn)生的性能損耗。

輕量級(jí)鎖獲取

如果同步對(duì)象鎖狀態(tài)為無(wú)鎖狀態(tài)(鎖標(biāo)志位為“01”狀態(tài),是否為偏向鎖為“0”),虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲(chǔ)鎖對(duì)象目前的 Mark Word 的拷貝,官方稱之為 Displaced Mark Word。這時(shí)候線程堆棧與對(duì)象頭的狀態(tài)如下圖所示:

拷貝對(duì)象頭中的 Mark Word 復(fù)制到鎖記錄(Lock Record)中。

拷貝成功后,虛擬機(jī)將使用 CAS 操作嘗試將對(duì)象的 Mark Word 更新為指向 Lock Record 的指針,并將 Lock record 里的 owner 指針指向 object mark word。

如果成功,當(dāng)前線程持有該對(duì)象鎖,將對(duì)象頭的 Mark Word 鎖標(biāo)志位設(shè)置為“00”,表示對(duì)象處于輕量級(jí)鎖定狀態(tài),執(zhí)行同步代碼塊。這時(shí)候線程堆棧與對(duì)象頭的狀態(tài)如下圖所示:

如果更新失敗,檢查對(duì)象頭的 Mark Word 是否指向當(dāng)前線程的棧幀,如果是,說(shuō)明當(dāng)前線程擁有鎖,直接執(zhí)行同步代碼塊。

如果否,說(shuō)明多個(gè)線程競(jìng)爭(zhēng)鎖,如果當(dāng)前只有一個(gè)等待線程,通過(guò)自旋嘗試獲取鎖。當(dāng)自旋超過(guò)一定次數(shù),或又來(lái)一個(gè)線程競(jìng)爭(zhēng)鎖,輕量級(jí)鎖膨脹為重量級(jí)鎖。重量級(jí)鎖使除了擁有鎖的線程以外的線程都阻塞,防止 CPU 空轉(zhuǎn),鎖標(biāo)志的狀態(tài)值變?yōu)椤?0”,Mark Word 中存儲(chǔ)的就是指向重量級(jí)鎖(互斥量)的指針,后面等待鎖的線程也要進(jìn)入阻塞狀態(tài)。

輕量級(jí)鎖解鎖

  • 輕量級(jí)鎖解鎖的時(shí)機(jī)是,當(dāng)前線程同步塊執(zhí)行完畢。
  • 通過(guò) CAS 操作嘗試把線程中復(fù)制的 Displaced Mark Word 對(duì)象替換當(dāng)前的 Mark Word。
  • 如果成功,整個(gè)同步過(guò)程完成
  • 如果失敗,說(shuō)明存在競(jìng)爭(zhēng),且鎖膨脹為重量級(jí)鎖。釋放鎖的同時(shí),會(huì)喚醒被掛起的線程。

重量級(jí)鎖

輕量級(jí)鎖適應(yīng)的場(chǎng)景是線程近乎交替執(zhí)行同步塊的情況,如果存在同一時(shí)間訪問(wèn)相同鎖對(duì)象時(shí)(第一個(gè)線程持有鎖,第二個(gè)線程自旋超過(guò)一定次數(shù)),輕量級(jí)鎖會(huì)膨脹為重量級(jí)鎖,Mark Word 的鎖標(biāo)記位更新為 10,Mark Word 指向互斥量(重量級(jí)鎖)。

重量級(jí)鎖是通過(guò)對(duì)象內(nèi)部的一個(gè)叫做監(jiān)視器鎖(monitor)來(lái)實(shí)現(xiàn)的,監(jiān)視器鎖本質(zhì)又是依賴于底層的操作系統(tǒng)的 Mutex Lock(互斥鎖)。操作系統(tǒng)實(shí)現(xiàn)線程之間的切換需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這個(gè)成本非常高,狀態(tài)之間的轉(zhuǎn)換需要相對(duì)比較長(zhǎng)的時(shí)間,這就是為什么 JDK 1.6 之前,synchronized 重量級(jí)鎖效率低的原因。

下圖是偏向鎖、輕量級(jí)鎖、重量級(jí)鎖之間轉(zhuǎn)換對(duì)象頭 Mark Word 數(shù)據(jù)轉(zhuǎn)變:

偏向鎖、輕量級(jí)鎖、重量級(jí)鎖之間轉(zhuǎn)換

網(wǎng)上有一個(gè)比較全的鎖升級(jí)過(guò)程:

鎖升級(jí)過(guò)程

鎖消除(Lock Elimination)

鎖消除說(shuō)的是虛擬機(jī)即時(shí)編譯器在運(yùn)行過(guò)程中,對(duì)于一些同步代碼,如果檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng)情況,就會(huì)刪除鎖。也就是說(shuō),即時(shí)編譯器根據(jù)情況刪除不必要的加鎖操作。
鎖消除的依據(jù)是逃逸分析。簡(jiǎn)單地說(shuō),逃逸分析就是分析對(duì)象的動(dòng)態(tài)作用域。分三種情況:

  • 不逃逸:對(duì)象的作用域只在本線程本方法
  • 方法逃逸:對(duì)象在方法內(nèi)定義后,被外部方法所引用
  • 線程逃逸:對(duì)象在方法內(nèi)定義后,被外部線程所引用

即時(shí)編譯器會(huì)針對(duì)對(duì)象的不同情況進(jìn)行優(yōu)化處理:

  • 對(duì)象棧上分配(Stack Allocations,HotSpot 不支持):直接在棧上創(chuàng)建對(duì)象。
  • 標(biāo)量替換(Scalar Replacement):將對(duì)象拆散,直接創(chuàng)建被方法使用的成員變量。前提是對(duì)象不會(huì)逃逸出方法范圍。
  • 同步消除(Synchronization Elimination):就是鎖消除,前提是對(duì)象不會(huì)逃逸出線程。

對(duì)于鎖消除來(lái)說(shuō),就是逃逸分析中,那些不會(huì)逃出線程的加鎖對(duì)象,就可以直接刪除同步鎖。

通過(guò)代碼看一個(gè)例子:

public void elimination1() {
    final Object lock = new Object();
    synchronized (lock) {
        System.out.println("lock 對(duì)象沒有只會(huì)作用域本線程,所以會(huì)鎖消除。");
    }
}

public String elimination2() {
    final StringBuffer sb = new StringBuffer();
    sb.append("Hello, ").append("World!");
    return sb.toString();
}

public StringBuffer notElimination() {
    final StringBuffer sb = new StringBuffer();
    sb.append("Hello, ").append("World!");
    return sb;
}

elimination1()中的鎖對(duì)象lock作用域只是方法內(nèi),沒有逃逸出線程,elimination2()中的sb也就這樣,所以這兩個(gè)方法的同步鎖都會(huì)被消除。但是notElimination()方法中的sb是方法返回值,可能會(huì)被其他方法修改或者其他線程修改,所以,單看這個(gè)方法,不會(huì)消除鎖,還得看調(diào)用方法。

鎖粗化(Lock Coarsening)

原則上,我們?cè)诰帉懘a的時(shí)候,要將同步塊作用域的作用范圍限制的盡量小。使得需要同步的操作數(shù)量盡量少,當(dāng)存在鎖競(jìng)爭(zhēng)時(shí),等待線程盡快獲取鎖。但是有時(shí)候,如果一系列的連續(xù)操作都對(duì)同一個(gè)對(duì)象反復(fù)加鎖和解鎖,甚至加鎖操作是出現(xiàn)在循環(huán)體中的,那即使沒有出現(xiàn)線程競(jìng)爭(zhēng),頻繁地進(jìn)行互斥同步操作也會(huì)導(dǎo)致不必要的性能損耗。如果虛擬機(jī)檢測(cè)到有一串零碎的操作都是對(duì)同一對(duì)象的加鎖,將會(huì)把加鎖同步的范圍擴(kuò)展(粗化)到整個(gè)操作序列的外部。
比如上面例子中的elimination2()方法中,StringBuffer的append是同步方法,頻繁操作時(shí),會(huì)進(jìn)行鎖粗化,最后結(jié)果會(huì)類似于(只是類似,不是真實(shí)情況):

public String elimination2() {
    final StringBuilder sb = new StringBuilder();
    synchronized (sb) {
        sb.append("Hello, ").append("World!");
        return sb.toString();
    }
}

或者

public synchronized String elimination3() {
    final StringBuilder sb = new StringBuilder();
    sb.append("Hello, ").append("World!");
    return sb.toString();
}

文末總結(jié)

  • 同步操作中影響性能的有兩點(diǎn):
    • 加鎖解鎖過(guò)程需要額外操作
    • 用戶態(tài)與內(nèi)核態(tài)之間轉(zhuǎn)換代價(jià)比較大
  • synchronized 在 JDK 1.6 中有大量?jī)?yōu)化:分級(jí)鎖(偏向鎖、輕量級(jí)鎖、重量級(jí)鎖)、鎖消除、鎖粗化等。
  • synchronized 復(fù)用了對(duì)象頭的 Mark Word 狀態(tài)位,實(shí)現(xiàn)不同等級(jí)的鎖實(shí)現(xiàn)。

到此這篇關(guān)于Java實(shí)現(xiàn)synchronized鎖同步機(jī)制的文章就介紹到這了,更多相關(guān)Java synchronized鎖同步 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java?17的一些新特性介紹

    Java?17的一些新特性介紹

    這篇文章主要介紹了Java?17的一些新特性介紹,Java添加了許多Java開發(fā)人員渴望的特性和改進(jìn),下文就來(lái)學(xué)習(xí)一下這些特性吧,需要的朋友可以參考一下
    2022-04-04
  • Java?file類中renameTo方法操作實(shí)例

    Java?file類中renameTo方法操作實(shí)例

    renameTo()方法是File類的一部分,renameTo()函數(shù)用于將文件的抽象路徑名重命名為給定的路徑名??,下面這篇文章主要給大家介紹了關(guān)于Java?file類中renameTo方法操作的相關(guān)資料,需要的朋友可以參考下
    2022-11-11
  • 解決問(wèn)題:Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources

    解決問(wèn)題:Failed to execute goal org.apache.m

    這篇文章主要給大家介紹了關(guān)于解決問(wèn)題:Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources的相關(guān)資料,文中將解決的辦法介紹的非常詳細(xì),需要的朋友可以參考下
    2023-03-03
  • Maven 錯(cuò)誤找不到符號(hào)的解決方法

    Maven 錯(cuò)誤找不到符號(hào)的解決方法

    這篇文章主要介紹了Maven 錯(cuò)誤找不到符號(hào)的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • Spring Cloud接口突然變慢的解決方案

    Spring Cloud接口突然變慢的解決方案

    在Spring Cloud項(xiàng)目中,接口突然變慢可能是由多種原因造成的,本文給大家介紹了一些可能的原因以及相應(yīng)的解決方案,通過(guò)代碼示例給大家講解的非常詳細(xì),需要的朋友可以參考下
    2024-01-01
  • SpringBoot中的自定義Starter解讀

    SpringBoot中的自定義Starter解讀

    這篇文章主要介紹了SpringBoot中的自定義Starter解讀,啟動(dòng)器模塊其實(shí)是一個(gè)空的jar文件,里面沒有什么類、接口,僅僅是提供輔助性依賴管理,這些依賴可能用于自動(dòng)裝配或者其他類庫(kù),需要的朋友可以參考下
    2023-12-12
  • Java圖形化界面設(shè)計(jì)之容器(JFrame)詳解

    Java圖形化界面設(shè)計(jì)之容器(JFrame)詳解

    這篇文章主要介紹了Java圖形化界面設(shè)計(jì)之容器(JFrame)詳解,條理清晰,依次介紹了Java基本類(JFC),AWT和Swing的區(qū)別,Swing基本框架,圖形化設(shè)計(jì)步驟以及組件容器的使用等相關(guān)內(nèi)容,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-11-11
  • Javafx簡(jiǎn)單實(shí)現(xiàn)【我的電腦資源管理器】效果

    Javafx簡(jiǎn)單實(shí)現(xiàn)【我的電腦資源管理器】效果

    這篇文章主要介紹了Javafx簡(jiǎn)單實(shí)現(xiàn)【我的電腦資源管理器】效果,涉及Javafx操作系統(tǒng)文件模擬資源管理器的實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-09-09
  • 詳解JAVA的封裝

    詳解JAVA的封裝

    Java面向?qū)ο蟮娜筇匦裕悍庋b、繼承、多態(tài)。下面對(duì)三大特性之一封裝進(jìn)行了總結(jié),需要的朋友可以參考下
    2017-04-04
  • Springboot上傳excel并將表格數(shù)據(jù)導(dǎo)入或更新mySql數(shù)據(jù)庫(kù)的過(guò)程

    Springboot上傳excel并將表格數(shù)據(jù)導(dǎo)入或更新mySql數(shù)據(jù)庫(kù)的過(guò)程

    這篇文章主要介紹了Springboot上傳excel并將表格數(shù)據(jù)導(dǎo)入或更新mySql數(shù)據(jù)庫(kù)的過(guò)程 ,本文以Controller開始,從導(dǎo)入過(guò)程開始講述,其中包括字典表的轉(zhuǎn)換,需要的朋友可以參考下
    2018-04-04

最新評(píng)論